Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
adrianwyatt committed May 3, 2023
1 parent 1295df7 commit b76a88d
Show file tree
Hide file tree
Showing 16 changed files with 117 additions and 329 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.CustomClient;
[SuppressMessage("Design", "CA1054:URI-like parameters should not be strings", Justification = "OpenAI users use strings")]
public abstract class OpenAIClientBase : IDisposable
{
protected readonly static HttpClientHandler DefaultHttpClientHandler = new() { CheckCertificateRevocationList = true };
protected static readonly HttpClientHandler DefaultHttpClientHandler = new() { CheckCertificateRevocationList = true };

/// <summary>
/// Logger
Expand All @@ -53,7 +53,7 @@ internal OpenAIClientBase(HttpClient? httpClient = null, ILogger? logger = null)
this.HTTPClient = httpClient;
}

this.HTTPClient.DefaultRequestHeaders.Add("User-Agent", HTTPUseragent);
this.HTTPClient.DefaultRequestHeaders.Add("User-Agent", HTTPUserAgent);
}

/// <summary>
Expand Down Expand Up @@ -186,7 +186,7 @@ protected virtual void Dispose(bool disposing)
#region private ================================================================================

// HTTP user agent sent to remote endpoints
private const string HTTPUseragent = "Microsoft Semantic Kernel";
private const string HTTPUserAgent = "Microsoft Semantic Kernel";

// Set to true to dispose of HttpClient when disposing. If HttpClient was passed in, then the caller can manage.
private readonly bool _disposeHttpClient = false;
Expand All @@ -198,12 +198,9 @@ private async Task<T> ExecutePostRequestAsync<T>(string url, string requestBody,
try
{
using HttpContent content = new StringContent(requestBody, Encoding.UTF8, "application/json");
HttpResponseMessage response = await this.HTTPClient.PostAsync(url, content, cancellationToken).ConfigureAwait(false);

if (response == null)
{
throw new AIException(AIException.ErrorCodes.NoResponse);
}
HttpResponseMessage response = await this.HTTPClient.PostAsync(url, content, cancellationToken).ConfigureAwait(false)
?? throw new AIException(AIException.ErrorCodes.NoResponse);

this.Log.LogTrace("HTTP response: {0} {1}", (int)response.StatusCode, response.StatusCode.ToString("G"));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ private void LogHttpMessage(HttpHeaders headers, Uri uri, string prefix)
{
if (this._logger.IsEnabled(LogLevel.Debug))
{
StringBuilder message = new StringBuilder();
StringBuilder message = new();
message.AppendLine($"{prefix} {uri}");
foreach (string headerName in this._headerNamesToLog)
{
Expand Down
4 changes: 2 additions & 2 deletions dotnet/src/Skills/Skills.MsGraph/TaskListSkill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public TaskListSkill(ITaskManagementConnector connector, ILogger<TaskListSkill>?
/// </summary>
public static DateTimeOffset GetNextDayOfWeek(DayOfWeek dayOfWeek, TimeSpan timeOfDay)
{
DateTimeOffset today = new DateTimeOffset(DateTime.Today);
DateTimeOffset today = new(DateTime.Today);
int nextDayOfWeekOffset = dayOfWeek - today.DayOfWeek;
if (nextDayOfWeekOffset <= 0)
{
Expand Down Expand Up @@ -83,7 +83,7 @@ public async Task AddTaskAsync(string title, SKContext context)
return;
}

TaskManagementTask task = new TaskManagementTask(
TaskManagementTask task = new(
id: Guid.NewGuid().ToString(),
title: title);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,69 +30,42 @@ public static class KernelChatGptPluginExtensions
/// <param name="kernel">Semantic Kernel instance.</param>
/// <param name="skillName">Skill name.</param>
/// <param name="url">Url to in which to retrieve the ChatGPT plugin.</param>
/// <param name="httpClient">Optional HttpClient to use for the request.</param>
/// <param name="httpClient">HttpClient to use for the request.</param>
/// <param name="authCallback">Optional callback for adding auth data to the API requests.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A list of all the semantic functions representing the skill.</returns>
public static async Task<IDictionary<string, ISKFunction>> ImportChatGptPluginSkillFromUrlAsync(
this IKernel kernel,
string skillName,
Uri url,
HttpClient? httpClient = null,
HttpClient httpClient,
AuthenticateRequestAsyncCallback? authCallback = null,
CancellationToken cancellationToken = default)
{
Verify.ValidSkillName(skillName);

HttpResponseMessage? response = null;
try
{
if (httpClient == null)
{
// TODO Fix this: throwing "The inner handler has not been assigned"
//using DefaultHttpRetryHandler retryHandler = new DefaultHttpRetryHandler(
// config: new HttpRetryConfig() { MaxRetryCount = 3 },
// log: null);

//using HttpClient client = new HttpClient(retryHandler, false);
using HttpClient client = new();
using HttpResponseMessage response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false);
response.EnsureSuccessStatusCode();

response = await client.GetAsync(url, cancellationToken).ConfigureAwait(false);
}
else
{
response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false);
}

response.EnsureSuccessStatusCode();

string gptPluginJson = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
string? openApiUrl = ParseOpenApiUrl(gptPluginJson);
string gptPluginJson = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
string? openApiUrl = ParseOpenApiUrl(gptPluginJson);

return await kernel.ImportOpenApiSkillFromUrlAsync(skillName, new Uri(openApiUrl), httpClient, authCallback, cancellationToken).ConfigureAwait(false);
}
finally
{
if (response != null)
{
response.Dispose();
}
}
return await kernel.ImportOpenApiSkillFromUrlAsync(skillName, new Uri(openApiUrl), httpClient, authCallback, cancellationToken).ConfigureAwait(false);
}

/// <summary>
/// Imports ChatGPT plugin from assembly resource.
/// </summary>
/// <param name="kernel">Semantic Kernel instance.</param>
/// <param name="skillName">Skill name.</param>
/// <param name="httpClient">Optional HttpClient to use for the request.</param>
/// <param name="httpClient">HttpClient to use for the request.</param>
/// <param name="authCallback">Optional callback for adding auth data to the API requests.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A list of all the semantic functions representing the skill.</returns>
public static async Task<IDictionary<string, ISKFunction>> ImportChatGptPluginSkillFromResourceAsync(
this IKernel kernel,
string skillName,
HttpClient? httpClient = null,
HttpClient httpClient,
AuthenticateRequestAsyncCallback? authCallback = null,
CancellationToken cancellationToken = default)
{
Expand All @@ -102,13 +75,10 @@ public static class KernelChatGptPluginExtensions

var resourceName = $"{skillName}.ai-plugin.json";

var stream = type.Assembly.GetManifestResourceStream(type, resourceName);
if (stream == null)
{
throw new MissingManifestResourceException($"Unable to load OpenApi skill from assembly resource '{resourceName}'.");
}
var stream = type.Assembly.GetManifestResourceStream(type, resourceName)
?? throw new MissingManifestResourceException($"Unable to load OpenApi skill from assembly resource '{resourceName}'.");

using StreamReader reader = new StreamReader(stream);
using StreamReader reader = new(stream);
string gptPluginJson = await reader.ReadToEndAsync().ConfigureAwait(false);

string? openApiUrl = ParseOpenApiUrl(gptPluginJson);
Expand All @@ -126,7 +96,7 @@ public static class KernelChatGptPluginExtensions
/// <param name="authCallback">Optional callback for adding auth data to the API requests.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A list of all the semantic functions representing the skill.</returns>
public async static Task<IDictionary<string, ISKFunction>> ImportChatGptPluginSkillSkillFromDirectoryAsync(
public static async Task<IDictionary<string, ISKFunction>> ImportChatGptPluginSkillSkillFromDirectoryAsync(
this IKernel kernel,
string parentDirectory,
string skillDirectoryName,
Expand Down Expand Up @@ -163,7 +133,7 @@ public static class KernelChatGptPluginExtensions
/// <param name="authCallback">Optional callback for adding auth data to the API requests.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A list of all the semantic functions representing the skill.</returns>
public async static Task<IDictionary<string, ISKFunction>> ImportChatGptPluginSkillSkillFromFileAsync(
public static async Task<IDictionary<string, ISKFunction>> ImportChatGptPluginSkillSkillFromFileAsync(
this IKernel kernel,
string skillName,
string filePath,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,54 +34,30 @@ public static class KernelOpenApiExtensions
/// <param name="kernel">Semantic Kernel instance.</param>
/// <param name="skillName">Skill name.</param>
/// <param name="url">Url to in which to retrieve the OpenAPI definition.</param>
/// <param name="httpClient">Optional HttpClient to use for the request.</param>
/// <param name="httpClient">HttpClient to use for the request.</param>
/// <param name="authCallback">Optional callback for adding auth data to the API requests.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A list of all the semantic functions representing the skill.</returns>
public static async Task<IDictionary<string, ISKFunction>> ImportOpenApiSkillFromUrlAsync(
this IKernel kernel,
string skillName,
Uri url,
HttpClient? httpClient = null,
HttpClient httpClient,
AuthenticateRequestAsyncCallback? authCallback = null,
CancellationToken cancellationToken = default)
{
Verify.ValidSkillName(skillName);

HttpResponseMessage? response = null;
try
{
if (httpClient == null)
{
// TODO Fix this: throwing "The inner handler has not been assigned"
//using DefaultHttpRetryHandler retryHandler = new DefaultHttpRetryHandler(
// config: new HttpRetryConfig() { MaxRetryCount = 3 },
// log: null);

//using HttpClient client = new HttpClient(retryHandler, false);
using HttpClient client = new HttpClient();

response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
}
else
{
response = await httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
}

response.EnsureSuccessStatusCode();
using HttpResponseMessage response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false);
response.EnsureSuccessStatusCode();

Stream stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
if (stream == null)
{
throw new MissingManifestResourceException($"Unable to load OpenApi skill from url '{url}'.");
}

return await kernel.RegisterOpenApiSkillAsync(stream, skillName, authCallback, cancellationToken).ConfigureAwait(false);
}
finally
Stream stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
if (stream == null)
{
response?.Dispose();
throw new MissingManifestResourceException($"Unable to load OpenApi skill from url '{url}'.");
}

return await kernel.RegisterOpenApiSkillAsync(stream, skillName, authCallback, cancellationToken).ConfigureAwait(false);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT License.

using System.Net.Http;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Microsoft.Graph;
using Microsoft.Graph.CallRecords;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.AI;
using Microsoft.SemanticKernel.Orchestration;
Expand All @@ -21,11 +23,12 @@
namespace SemanticKernel.Service.Controllers;

[ApiController]
public class SemanticKernelController : ControllerBase
public class SemanticKernelController : ControllerBase, IDisposable
{
private readonly ILogger<SemanticKernelController> _logger;
private readonly PromptSettings _promptSettings;
private readonly ServiceOptions _options;
private readonly List<IDisposable> _disposables;

public SemanticKernelController(
IOptions<ServiceOptions> options,
Expand All @@ -35,6 +38,7 @@ public class SemanticKernelController : ControllerBase
this._logger = logger;
this._options = options.Value;
this._promptSettings = promptSettings;
this._disposables = new List<IDisposable>();
}

/// <summary>
Expand Down Expand Up @@ -140,12 +144,16 @@ public class SemanticKernelController : ControllerBase
/// </summary>
private async Task RegisterPlannerSkillsAsync(CopilotChatPlanner planner, PlannerOptions options, OpenApiSkillsAuthHeaders openApiSkillsAuthHeaders)
{
// Register the Klarna shopping skill with the planner's kernel.
await planner.Kernel.ImportOpenApiSkillFromFileAsync(
skillName: "KlarnaShoppingSkill",
filePath: Path.Combine(System.IO.Directory.GetCurrentDirectory(), @"Skills/OpenApiSkills/KlarnaSkill/openapi.json"));
// Register the Klarna shopping ChatGPT plugin with the planner's kernel.
using HttpClient importHttpClient = new HttpClient();
importHttpClient.DefaultRequestHeaders.Add("User-Agent", "Microsoft.CopilotChat");
await planner.Kernel.ImportChatGptPluginSkillFromUrlAsync("KlarnaShoppingSkill", new Uri("https://www.klarna.com/.well-known/ai-plugin.json"), importHttpClient);

// Register authenticated OpenAPI skills with the planner's kernel if the request includes an auth header for an OpenAPI skill.
//
// Register authenticated skills with the planner's kernel only if the request includes an auth header for the skill.
//

// GitHub
if (!string.IsNullOrWhiteSpace(openApiSkillsAuthHeaders.GithubAuthentication))
{
this._logger.LogInformation("Enabling GitHub skill.");
Expand All @@ -156,19 +164,56 @@ private async Task RegisterPlannerSkillsAsync(CopilotChatPlanner planner, Planne
authCallback: authenticationProvider.AuthenticateRequestAsync);
}

// Microsoft Graph
if (!string.IsNullOrWhiteSpace(openApiSkillsAuthHeaders.GraphAuthentication))
{
this._logger.LogInformation("Enabling Microsoft Graph skill(s).");
BearerAuthenticationProvider authenticationProvider = new(() => Task.FromResult(openApiSkillsAuthHeaders.GraphAuthentication));

IList<DelegatingHandler> graphMiddlewareHandlers =
GraphClientFactory.CreateDefaultHandlers(new DelegateAuthenticationProvider(authenticationProvider.AuthenticateRequestAsync));
graphMiddlewareHandlers.Add(new MsGraphClientLoggingHandler(this._logger));

GraphServiceClient graphServiceClient = new(GraphClientFactory.Create(graphMiddlewareHandlers));

GraphServiceClient graphServiceClient = this.CreateGraphServiceClient(authenticationProvider.AuthenticateRequestAsync);
TaskListSkill todoSkill = new(new MicrosoftToDoConnector(graphServiceClient));
planner.Kernel.ImportSkill(todoSkill, "todo");
}
}

/// <summary>
/// Create a Microsoft Graph service client.
/// </summary>
/// <param name="authenticateRequestAsyncDelegate">The delegate to authenticate the request.</param>
private GraphServiceClient CreateGraphServiceClient(AuthenticateRequestAsyncDelegate authenticateRequestAsyncDelegate)
{
MsGraphClientLoggingHandler graphLoggingHandler = new(this._logger);
this._disposables.Add(graphLoggingHandler);

IList<DelegatingHandler> graphMiddlewareHandlers =
GraphClientFactory.CreateDefaultHandlers(new DelegateAuthenticationProvider(authenticateRequestAsyncDelegate));
graphMiddlewareHandlers.Add(graphLoggingHandler);

HttpClient graphHttpClient = GraphClientFactory.Create(graphMiddlewareHandlers);
this._disposables.Add(graphHttpClient);

GraphServiceClient graphServiceClient = new(graphHttpClient);
return graphServiceClient;
}

/// <summary>
/// Dispose of the object.
/// </summary>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
foreach (IDisposable disposable in this._disposables)
{
disposable.Dispose();
}
}
}

// <inheritdoc />
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
this.Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
15 changes: 0 additions & 15 deletions samples/apps/copilot-chat-app/webapi/CopilotChatApi.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,6 @@
<UserSecretsId>aspnet-SKWebApi-1581687a-bee4-40ea-a886-ce22524aea88</UserSecretsId>
</PropertyGroup>

<ItemGroup>
<None Include="PlannerSkills\Summarize\config.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="PlannerSkills\Summarize\skprompt.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="PlannerSkills\Translate\config.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="PlannerSkills\Translate\skprompt.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.2.2" />
<PackageReference Include="Azure.Identity" Version="1.8.2" />
Expand Down

0 comments on commit b76a88d

Please sign in to comment.