-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
.Net: Add factory for customizing OpenAPI plugins responses (#10106)
### Motivation and Context Currently, it's impossible to access the HTTP response and response content headers returned by a REST API requested from OpenAPI plugins. ### Description This PR adds `RestApiOperationResponseFactory`, which can be used to customize the responses of OpenAPI plugins before returning them to the caller. The customization may include modifying the original response by adding response headers, changing the response content, adjusting the schema, or providing a completely new response. Closes: #9986
- Loading branch information
1 parent
4a70658
commit 941ee64
Showing
12 changed files
with
428 additions
and
8 deletions.
There are no files selected for viewing
110 changes: 110 additions & 0 deletions
110
dotnet/samples/Concepts/Plugins/OpenApiPlugin_RestApiOperationResponseFactory.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
|
||
using System.Net; | ||
using System.Text; | ||
using Microsoft.SemanticKernel; | ||
using Microsoft.SemanticKernel.Plugins.OpenApi; | ||
|
||
namespace Plugins; | ||
|
||
/// <summary> | ||
/// Sample shows how to register the <see cref="RestApiOperationResponseFactory"/> to transform existing or create new <see cref="RestApiOperationResponse"/>. | ||
/// </summary> | ||
public sealed class OpenApiPlugin_RestApiOperationResponseFactory(ITestOutputHelper output) : BaseTest(output) | ||
{ | ||
private readonly HttpClient _httpClient = new(new StubHttpHandler(InterceptRequestAndCustomizeResponseAsync)); | ||
|
||
[Fact] | ||
public async Task IncludeResponseHeadersToOperationResponseAsync() | ||
{ | ||
Kernel kernel = new(); | ||
|
||
// Register the operation response factory and the custom HTTP client | ||
OpenApiFunctionExecutionParameters executionParameters = new() | ||
{ | ||
RestApiOperationResponseFactory = IncludeHeadersIntoRestApiOperationResponseAsync, | ||
HttpClient = this._httpClient | ||
}; | ||
|
||
// Create OpenAPI plugin | ||
KernelPlugin plugin = await OpenApiKernelPluginFactory.CreateFromOpenApiAsync("RepairService", "Resources/Plugins/RepairServicePlugin/repair-service.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>()!; | ||
|
||
// Display the 'repair-id' header value | ||
Console.WriteLine(response.Headers!["repair-id"].First()); | ||
} | ||
|
||
/// <summary> | ||
/// A custom factory to transform the operation response. | ||
/// </summary> | ||
/// <param name="context">The context for the <see cref="RestApiOperationResponseFactory"/>.</param> | ||
/// <param name="cancellationToken">The cancellation token.</param> | ||
/// <returns>The transformed operation response.</returns> | ||
private static async Task<RestApiOperationResponse> IncludeHeadersIntoRestApiOperationResponseAsync(RestApiOperationResponseFactoryContext context, CancellationToken cancellationToken) | ||
{ | ||
// Create the response using the internal factory | ||
RestApiOperationResponse response = await context.InternalFactory(context, cancellationToken); | ||
|
||
// Obtain the 'repair-id' header value from the HTTP response and include it in the operation response only for the 'createRepair' operation | ||
if (context.Operation.Id == "createRepair" && context.Response.Headers.TryGetValues("repair-id", out IEnumerable<string>? values)) | ||
{ | ||
response.Headers ??= new Dictionary<string, IEnumerable<string>>(); | ||
response.Headers["repair-id"] = values; | ||
} | ||
|
||
// Return the modified response that will be returned to the caller | ||
return response; | ||
} | ||
|
||
/// <summary> | ||
/// A custom HTTP handler to intercept HTTP requests and return custom responses. | ||
/// </summary> | ||
/// <param name="request">The original HTTP request.</param> | ||
/// <returns>The custom HTTP response.</returns> | ||
private static async Task<HttpResponseMessage> InterceptRequestAndCustomizeResponseAsync(HttpRequestMessage request) | ||
{ | ||
// Return a mock response that includes the 'repair-id' header for the 'createRepair' operation | ||
if (request.RequestUri!.AbsolutePath == "/repairs" && request.Method == HttpMethod.Post) | ||
{ | ||
return new HttpResponseMessage(HttpStatusCode.Created) | ||
{ | ||
Content = new StringContent("Success", Encoding.UTF8, "application/json"), | ||
Headers = | ||
{ | ||
{ "repair-id", "repair-12345" } | ||
} | ||
}; | ||
} | ||
|
||
return new HttpResponseMessage(HttpStatusCode.NoContent); | ||
} | ||
|
||
private sealed class StubHttpHandler(Func<HttpRequestMessage, Task<HttpResponseMessage>> requestHandler) : DelegatingHandler() | ||
{ | ||
private readonly Func<HttpRequestMessage, Task<HttpResponseMessage>> _requestHandler = requestHandler; | ||
|
||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) | ||
{ | ||
return await this._requestHandler(request); | ||
} | ||
} | ||
|
||
protected override void Dispose(bool disposing) | ||
{ | ||
base.Dispose(disposing); | ||
this._httpClient.Dispose(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 16 additions & 0 deletions
16
dotnet/src/Functions/Functions.OpenApi/RestApiOperationResponseFactory.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
|
||
using System.Diagnostics.CodeAnalysis; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace Microsoft.SemanticKernel.Plugins.OpenApi; | ||
|
||
/// <summary> | ||
/// Represents a factory for creating instances of the <see cref="RestApiOperationResponse"/>. | ||
/// </summary> | ||
/// <param name="context">The context that contains the operation details.</param> | ||
/// <param name="cancellationToken">The cancellation token used to signal cancellation.</param> | ||
/// <returns>A task that represents the asynchronous operation, containing an instance of <see cref="RestApiOperationResponse"/>.</returns> | ||
[Experimental("SKEXP0040")] | ||
public delegate Task<RestApiOperationResponse> RestApiOperationResponseFactory(RestApiOperationResponseFactoryContext context, CancellationToken cancellationToken = default); |
48 changes: 48 additions & 0 deletions
48
dotnet/src/Functions/Functions.OpenApi/RestApiOperationResponseFactoryContext.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
|
||
using System.Diagnostics.CodeAnalysis; | ||
using System.Net.Http; | ||
|
||
namespace Microsoft.SemanticKernel.Plugins.OpenApi; | ||
|
||
/// <summary> | ||
/// Represents the context for the <see cref="RestApiOperationResponseFactory"/>."/> | ||
/// </summary> | ||
[Experimental("SKEXP0040")] | ||
public sealed class RestApiOperationResponseFactoryContext | ||
{ | ||
/// <summary> | ||
/// Initializes a new instance of the <see cref="RestApiOperationResponseFactoryContext"/> class. | ||
/// </summary> | ||
/// <param name="operation">The REST API operation.</param> | ||
/// <param name="request">The HTTP request message.</param> | ||
/// <param name="response">The HTTP response message.</param> | ||
/// <param name="internalFactory">The internal factory to create instances of the <see cref="RestApiOperationResponse"/>.</param> | ||
internal RestApiOperationResponseFactoryContext(RestApiOperation operation, HttpRequestMessage request, HttpResponseMessage response, RestApiOperationResponseFactory internalFactory) | ||
{ | ||
this.InternalFactory = internalFactory; | ||
this.Operation = operation; | ||
this.Request = request; | ||
this.Response = response; | ||
} | ||
|
||
/// <summary> | ||
/// The REST API operation. | ||
/// </summary> | ||
public RestApiOperation Operation { get; } | ||
|
||
/// <summary> | ||
/// The HTTP request message. | ||
/// </summary> | ||
public HttpRequestMessage Request { get; } | ||
|
||
/// <summary> | ||
/// The HTTP response message. | ||
/// </summary> | ||
public HttpResponseMessage Response { get; } | ||
|
||
/// <summary> | ||
/// The internal factory to create instances of the <see cref="RestApiOperationResponse"/>. | ||
/// </summary> | ||
public RestApiOperationResponseFactory InternalFactory { get; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.