-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #339 from microsoft/mistral-support-and-extended-m…
…iddleware Extended middleware and Mistral Chat Completions support
- Loading branch information
Showing
27 changed files
with
675 additions
and
336 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
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
37 changes: 37 additions & 0 deletions
37
src/AzureAIProxy/Middleware/BearerTokenAuthenticationHandler.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,37 @@ | ||
using System.Security.Claims; | ||
using System.Text.Encodings.Web; | ||
using Microsoft.AspNetCore.Authentication; | ||
using Microsoft.Extensions.Options; | ||
using AzureAIProxy.Services; | ||
|
||
namespace AzureAIProxy.Middleware; | ||
|
||
public class BearerTokenAuthenticationHandler( | ||
IOptionsMonitor<ProxyAuthenticationOptions> options, | ||
IAuthorizeService authorizeService, | ||
ILoggerFactory logger, | ||
UrlEncoder encoder | ||
) : AuthenticationHandler<ProxyAuthenticationOptions>(options, logger, encoder) | ||
{ | ||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync() | ||
{ | ||
if (!Request.Headers.TryGetValue("Authorization", out var apiKeyValues)) | ||
return AuthenticateResult.Fail("Missing API key is empty."); | ||
|
||
// Extract the API key from the Authorization header | ||
var apiKey = apiKeyValues.ToString().Split(" ").Last(); // Convert StringValues to string | ||
if (string.IsNullOrWhiteSpace(apiKey)) | ||
return AuthenticateResult.Fail("API key is empty."); | ||
|
||
var requestContext = await authorizeService.IsUserAuthorizedAsync(apiKey); | ||
if (requestContext is null) | ||
return AuthenticateResult.Fail("Authentication failed."); | ||
|
||
Context.Items["RequestContext"] = requestContext; | ||
|
||
var identity = new ClaimsIdentity(null, nameof(BearerTokenAuthenticationHandler)); | ||
var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity), Scheme.Name); | ||
|
||
return AuthenticateResult.Success(ticket); | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
src/AzureAIProxy/Middleware/BearerTokenAuthorizeAttribute.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,12 @@ | ||
using Microsoft.AspNetCore.Authorization; | ||
using AzureAIProxy.Middleware; | ||
using System.Diagnostics; | ||
|
||
namespace AzureAIProxy; | ||
|
||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] | ||
[DebuggerDisplay("{ToString(),nq}")] | ||
public class BearerTokenAuthorizeAttribute : AuthorizeAttribute | ||
{ | ||
public BearerTokenAuthorizeAttribute() => AuthenticationSchemes = ProxyAuthenticationOptions.BearerTokenScheme; | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
using AzureAIProxy.Shared.Database; | ||
using System.Text.Json; | ||
|
||
namespace AzureAIProxy.Middleware; | ||
|
||
public class LoadProperties(RequestDelegate next) | ||
{ | ||
public async Task InvokeAsync(HttpContext context) | ||
{ | ||
JsonDocument? jsonDoc = null; | ||
try | ||
{ | ||
if (!context.Request.HasFormContentType && | ||
context.Request.ContentType is not null && | ||
context.Request.ContentType.Contains("application/json", StringComparison.InvariantCultureIgnoreCase)) | ||
{ | ||
using var reader = new StreamReader(context.Request.Body); | ||
string json = await reader.ReadToEndAsync(); | ||
if (!string.IsNullOrWhiteSpace(json)) | ||
{ | ||
try | ||
{ | ||
jsonDoc = JsonDocument.Parse(json); | ||
} | ||
catch (JsonException) | ||
{ | ||
await OpenAIErrorResponse.BadRequest($"Invalid JSON in request body: {json}").WriteAsync(context); | ||
return; | ||
} | ||
|
||
jsonDoc = JsonDocument.Parse(json); | ||
|
||
context.Items["IsStreaming"] = IsStreaming(jsonDoc); | ||
context.Items["ModelName"] = GetModelName(jsonDoc); | ||
} | ||
} | ||
|
||
context.Items["requestPath"] = context.Request.Path.Value!.Split("/api/v1/").Last(); | ||
context.Items["jsonDoc"] = jsonDoc; | ||
|
||
await next(context); | ||
} | ||
finally | ||
{ | ||
jsonDoc?.Dispose(); | ||
} | ||
} | ||
|
||
private static bool IsStreaming(JsonDocument requestJsonDoc) | ||
{ | ||
return requestJsonDoc.RootElement.ValueKind == JsonValueKind.Object | ||
&& requestJsonDoc.RootElement.TryGetProperty("stream", out JsonElement streamElement) | ||
&& ( | ||
streamElement.ValueKind == JsonValueKind.True | ||
|| streamElement.ValueKind == JsonValueKind.False | ||
) | ||
&& streamElement.GetBoolean(); | ||
} | ||
|
||
private static string? GetModelName(JsonDocument requestJsonDoc) | ||
{ | ||
return requestJsonDoc.RootElement.ValueKind == JsonValueKind.Object && | ||
requestJsonDoc.RootElement.TryGetProperty("model", out JsonElement modelElement) && | ||
modelElement.ValueKind == JsonValueKind.String | ||
? modelElement.GetString() | ||
: null; | ||
} | ||
} |
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,38 @@ | ||
using AzureAIProxy.Shared.Database; | ||
using System.Text.Json; | ||
|
||
namespace AzureAIProxy.Middleware; | ||
|
||
public class MaxTokensHandler(RequestDelegate next) | ||
{ | ||
public async Task InvokeAsync(HttpContext context) | ||
{ | ||
RequestContext? requestContext = context.Items["RequestContext"]! as RequestContext; | ||
JsonDocument? jsonDoc = context.Items["jsonDoc"]! as JsonDocument; | ||
|
||
if (requestContext is not null && jsonDoc is not null) | ||
{ | ||
int? maxTokens = GetMaxTokens(jsonDoc); | ||
if (maxTokens.HasValue && maxTokens > requestContext.MaxTokenCap && requestContext.MaxTokenCap > 0) | ||
{ | ||
await OpenAIErrorResponse.BadRequest( | ||
$"max_tokens exceeds the event max token cap of {requestContext.MaxTokenCap}" | ||
).WriteAsync(context); | ||
return; | ||
} | ||
} | ||
|
||
await next(context); | ||
} | ||
|
||
private static int? GetMaxTokens(JsonDocument requestJsonDoc) | ||
{ | ||
return | ||
requestJsonDoc.RootElement.ValueKind == JsonValueKind.Object | ||
&& requestJsonDoc.RootElement.TryGetProperty("max_tokens", out var maxTokensElement) | ||
&& maxTokensElement.ValueKind == JsonValueKind.Number | ||
&& maxTokensElement.TryGetInt32(out int maxTokens) | ||
? maxTokens | ||
: null; | ||
} | ||
} |
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,31 @@ | ||
using System.Net; | ||
using Microsoft.AspNetCore.Http.HttpResults; | ||
|
||
namespace AzureAIProxy.Middleware; | ||
|
||
public class OpenAIErrorResponse(string message, HttpStatusCode statusCode) | ||
{ | ||
private readonly JsonHttpResult<ErrorResponse> innerResult = TypedResults.Json( | ||
new ErrorResponse(new ErrorDetails(statusCode.ToString(), message, (int)statusCode)), | ||
statusCode: (int)statusCode | ||
); | ||
|
||
public static OpenAIErrorResponse BadRequest(string message) => | ||
new(message, HttpStatusCode.BadRequest); | ||
|
||
public static OpenAIErrorResponse TooManyRequests(string message) => | ||
new(message, HttpStatusCode.TooManyRequests); | ||
|
||
public static OpenAIErrorResponse Unauthorized(string message) => | ||
new(message, HttpStatusCode.Unauthorized); | ||
|
||
public async Task WriteAsync(HttpContext httpContext) | ||
{ | ||
httpContext.Response.StatusCode = (int)innerResult.StatusCode!; | ||
await httpContext.Response.WriteAsJsonAsync(innerResult.Value); | ||
} | ||
|
||
record ErrorDetails(string Code, string Message, int Status); | ||
|
||
record ErrorResponse(ErrorDetails Error); | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,22 @@ | ||
using AzureAIProxy.Shared.Database; | ||
|
||
namespace AzureAIProxy.Middleware; | ||
|
||
public class RateLimiterHandler(RequestDelegate next) | ||
{ | ||
private const int RateLimitStatusCode = 429; | ||
private readonly RequestDelegate _next = next; | ||
|
||
public async Task InvokeAsync(HttpContext context) | ||
{ | ||
if (context.Items.TryGetValue("RateLimited", out var rateLimit) && rateLimit is true) | ||
RequestContext? requestContext = context.Items["RequestContext"] as RequestContext; | ||
|
||
if (requestContext is not null && requestContext.RateLimitExceed) | ||
{ | ||
var dailyRequestCap = context.Items["DailyRequestCap"] ?? 0; | ||
context.Response.StatusCode = RateLimitStatusCode; // Too Many Requests | ||
await context.Response.WriteAsJsonAsync( | ||
new | ||
{ | ||
code = RateLimitStatusCode, | ||
message = $"The event daily request rate of {dailyRequestCap} calls has been exceeded. Requests are disabled until UTC midnight." | ||
} | ||
); | ||
await OpenAIErrorResponse.TooManyRequests( | ||
$"The event daily request rate of {requestContext.DailyRequestCap} calls has been exceeded. Requests are disabled until UTC midnight." | ||
).WriteAsync(context); | ||
} | ||
else | ||
{ | ||
await _next(context); | ||
await next(context); | ||
} | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
using System.Text.Json.Serialization; | ||
|
||
namespace AzureAIProxy.Services; | ||
namespace AzureAIProxy.Models; | ||
|
||
public record AttendeeKey([property: JsonPropertyName("api_key")] string ApiKey, bool Active); |
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,7 @@ | ||
namespace AzureAIProxy.Models; | ||
|
||
public class RequestHeader(string key, string value) | ||
{ | ||
public string Key { get; set; } = key; | ||
public string Value { get; set; } = value; | ||
} |
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.