Skip to content

Commit

Permalink
feat: SK 1.0 upgrade
Browse files Browse the repository at this point in the history
  • Loading branch information
aymenfurter committed Jan 2, 2024
1 parent c66fa4b commit 133d39a
Show file tree
Hide file tree
Showing 51 changed files with 1,128 additions and 1,218 deletions.
33 changes: 1 addition & 32 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "Transcript Search Dev Image",
"image": "mcr.microsoft.com/devcontainers/universal:linux",
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",

"features": {
"ghcr.io/devcontainers/features/azure-cli:1": {},
Expand All @@ -9,36 +9,5 @@
"ghcr.io/devcontainers-contrib/features/angular-cli:2": {},
"ghcr.io/azure/azure-dev/azd:0": {},
"ghcr.io/devcontainers/features/python:1": {}
},
"containerEnv": {
"ACS_INSTANCE": "${localEnv:ACS_INSTANCE}",
"ACS_KEY": "${localEnv:ACS_KEY}",
"AZURE_OPENAI_API_KEY": "${localEnv:AZURE_OPENAI_API_KEY}",
"AZURE_OPENAI_DEPLOYMENT_NAME": "${localEnv:AZURE_OPENAI_DEPLOYMENT_NAME}",
"AZURE_OPENAI_ENDPOINT": "${localEnv:AZURE_OPENAI_ENDPOINT}"
},
"remoteEnv": {
"ACS_INSTANCE": "${localEnv:ACS_INSTANCE}",
"ACS_KEY": "${localEnv:ACS_KEY}",
"AZURE_OPENAI_API_KEY": "${localEnv:AZURE_OPENAI_API_KEY}",
"AZURE_OPENAI_DEPLOYMENT_NAME": "${localEnv:AZURE_OPENAI_DEPLOYMENT_NAME}",
"AZURE_OPENAI_ENDPOINT": "${localEnv:AZURE_OPENAI_ENDPOINT}"
}

// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [5000, 5001],
// "portsAttributes": {
// "5001": {
// "protocol": "https"
// }
// }

// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "dotnet restore",

// Configure tool-specific properties.
// "customizations": {},

// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
2 changes: 1 addition & 1 deletion azure.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: todo-java-mongo-aca
name: azure-transcript-search-openai
services:
web:
project: ./webui
Expand Down
2 changes: 1 addition & 1 deletion webapi/AdapterWithErrorHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
using Microsoft.Bot.Connector.Authentication;
using Microsoft.Extensions.Logging;

namespace TeamsBot
namespace AzureVideoChat.Bots
{
public class AdapterWithErrorHandler : CloudAdapter
{
Expand Down
211 changes: 102 additions & 109 deletions webapi/TranscriptCopilot/Bots/TeamsBot.cs → webapi/Bots/TeamsBot.cs
Original file line number Diff line number Diff line change
@@ -1,109 +1,102 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
//

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Schema;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.AI;
using Microsoft.SemanticKernel.Orchestration;
using Microsoft.SemanticKernel.SkillDefinition;
using SemanticKernel.Service.CopilotChat.Controllers;
using SemanticKernel.Service.CopilotChat.Skills.ChatSkills;
using SemanticKernel.Service.Models;

namespace TeamsBot.Bots
{
public class TeamsBot : ActivityHandler
{
private readonly ChatService _chatService;
private readonly BotState _conversationState;

public TeamsBot(ChatService chatService, ConversationState conversationState)
{
_chatService = chatService ?? throw new ArgumentNullException(nameof(chatService));
_conversationState = conversationState;
}

protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
var conversationStateAccessors = _conversationState.CreateProperty<ConversationData>(nameof(ConversationData));
var conversationData = await conversationStateAccessors.GetAsync(turnContext, () => new ConversationData());

conversationData.ConversationHistory.Add(turnContext.Activity.Text);
var variables = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("History", string.Join("\n\n", conversationData.ConversationHistory))
};

var chatRequest = new ChatRequest { Input = turnContext.Activity.Text, Variables = variables };

await turnContext.SendActivitiesAsync(new Activity[] { new Activity { Type = ActivityTypes.Typing } }, cancellationToken);

SKContext chatResult;
try
{
chatResult = await _chatService.ExecuteChatAsync(chatRequest);
}
catch
{
await turnContext.SendActivityAsync("An error occurred while processing the request.", cancellationToken: cancellationToken);
return;
}

ChatResponse reply = _chatService.CreateChatResponse(chatResult);
var links = reply.Variables.FirstOrDefault(kvp => kvp.Key == "link").Value;
var replyText = reply.Value;
replyText = ConvertLinksToMarkdown(replyText);

await turnContext.SendActivityAsync(MessageFactory.Text(replyText), cancellationToken);

if (!string.IsNullOrEmpty(links) && !links.Contains("QH2-TGUlwu4"))
{
links = links.Replace(" ", Environment.NewLine);
links = links.Replace("/embed", "/v");
var youtubeLinks = links.Split(Environment.NewLine);

var card = new HeroCard
{
Title = "Sources",
Subtitle = "Relevant YouTube Links",
Buttons = youtubeLinks.Select(link => new CardAction(ActionTypes.OpenUrl, link, value: link)).ToList()
};

var attachment = MessageFactory.Attachment(card.ToAttachment());
await turnContext.SendActivityAsync(attachment, cancellationToken);
}

await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
}
public static string ConvertLinksToMarkdown(string html)
{
if (string.IsNullOrEmpty(html))
{
return string.Empty;
}

string pattern = "<a [^>]*href=[“\"](https?[^“\"]+)[“\"][^>]*>([^<]+)<\\/a>";
return Regex.Replace(html, pattern, "[$2]($1)");
}

protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
var welcomeText = "Hello and welcome!";
foreach (var member in membersAdded)
{
if (member.Id != turnContext.Activity.Recipient.Id)
{
await turnContext.SendActivityAsync(MessageFactory.Text(welcomeText, welcomeText), cancellationToken);
}
}
}
}
}
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Schema;
using AzureVideoChat.Controllers;
using AzureVideoChat.Models;
using AzureVideoChat.Services;

namespace AzureVideoChat.Bots
{
public class TeamsBot : ActivityHandler
{
private readonly ChatService _chatService;
private readonly BotState _conversationState;

public TeamsBot(ChatService chatService, ConversationState conversationState)
{
_chatService = chatService ?? throw new ArgumentNullException(nameof(chatService));
_conversationState = conversationState;
}

protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
var conversationStateAccessors = _conversationState.CreateProperty<ConversationData>(nameof(ConversationData));
var conversationData = await conversationStateAccessors.GetAsync(turnContext, () => new ConversationData());

conversationData.ConversationHistory.Add(turnContext.Activity.Text);
var variables = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("History", string.Join("\n\n", conversationData.ConversationHistory))
};

var chatRequest = new ChatRequest { Input = turnContext.Activity.Text, Variables = variables };

await turnContext.SendActivitiesAsync(new Activity[] { new Activity { Type = ActivityTypes.Typing } }, cancellationToken);

ChatServiceResponse chatResult;
try
{
chatResult = await _chatService.ExecuteChatAsync(chatRequest);
}
catch
{
await turnContext.SendActivityAsync("An error occurred while processing the request.", cancellationToken: cancellationToken);
return;
}

ChatResponse reply = _chatService.CreateChatResponse(chatResult.Result, chatResult.ContextVariables);
var links = reply.Variables.FirstOrDefault(kvp => kvp.Key == "link").Value;
var replyText = reply.Value;
replyText = ConvertLinksToMarkdown(replyText);

await turnContext.SendActivityAsync(MessageFactory.Text(replyText), cancellationToken);

if (!string.IsNullOrEmpty(links))
{
var references = links.Split(Environment.NewLine);

var card = new HeroCard
{
Title = "Sources",
Subtitle = "Relevant Links",
Buttons = references.Select(link => new CardAction(ActionTypes.OpenUrl, link, value: link)).ToList()
};

var attachment = MessageFactory.Attachment(card.ToAttachment());
await turnContext.SendActivityAsync(attachment, cancellationToken);
}

await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
}
public static string ConvertLinksToMarkdown(string html)
{
if (string.IsNullOrEmpty(html))
{
return string.Empty;
}

string pattern = "<a [^>]*href=[“\"](https?[^“\"]+)[“\"][^>]*>([^<]+)<\\/a>";
return Regex.Replace(html, pattern, "[$2]($1)");
}

protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
var welcomeText = "Hello and welcome!";
foreach (var member in membersAdded)
{
if (member.Id != turnContext.Activity.Recipient.Id)
{
await turnContext.SendActivityAsync(MessageFactory.Text(welcomeText, welcomeText), cancellationToken);
}
}
}
}
}
43 changes: 43 additions & 0 deletions webapi/ConfigServiceExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using AzureVideoChat.Options;
using AzureVideoChat.Extensions;

internal static class ConfigServicesExtensions
{

internal static IServiceCollection AddOptions(this IServiceCollection services, ConfigurationManager configuration)
{
// General configuration
services.AddOptions<ServiceOptions>()
.Bind(configuration.GetSection(ServiceOptions.PropertyName))
.ValidateDataAnnotations()
.ValidateOnStart()
.PostConfigure(options => PropertyTrimmer.TrimStringProperties(options));

return services;
}

internal static IServiceCollection AddCors(this IServiceCollection services)
{
IConfiguration configuration = services.BuildServiceProvider().GetRequiredService<IConfiguration>();
string[] allowedOrigins = configuration.GetSection("AllowedOrigins").Get<string[]>() ?? Array.Empty<string>();
if (allowedOrigins.Length > 0)
{
services.AddCors(options =>
{
options.AddDefaultPolicy(
policy =>
{
policy.WithOrigins(allowedOrigins)
.AllowAnyHeader();
});
});
}

return services;
}
}
8 changes: 1 addition & 7 deletions webapi/ConfigurationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ namespace SemanticKernel.Service;

internal static class ConfigExtensions
{
/// <summary>
/// Build the configuration for the service.
/// </summary>
public static IHostBuilder AddConfiguration(this IHostBuilder host)
{
string? environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
Expand All @@ -36,18 +33,15 @@ public static IHostBuilder AddConfiguration(this IHostBuilder host)
optional: true,
reloadOnChange: true);

// For settings from Key Vault, see https://learn.microsoft.com/en-us/aspnet/core/security/key-vault-configuration?view=aspnetcore-8.0
string? keyVaultUri = builderContext.Configuration["KeyVaultUri"];
if (!string.IsNullOrWhiteSpace(keyVaultUri))
{
configBuilder.AddAzureKeyVault(
new Uri(keyVaultUri),
new DefaultAzureCredential());

// for more information on how to use DefaultAzureCredential, see https://learn.microsoft.com/en-us/dotnet/api/azure.identity.defaultazurecredential?view=azure-dotnet
}
});

return host;
}
}
}
49 changes: 49 additions & 0 deletions webapi/Connectors/AISearchClientManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using Azure;
using Azure.Search.Documents;
using System;
using System.Collections.Concurrent;
using System.Net.Http;
using Azure.Search.Documents.Indexes;
using Azure.Core.Pipeline;

namespace AzureVideoChat.Connectors.Memory.AzureCognitiveSearchVector
{
public class AISearchClientManager
{
private readonly SearchIndexClient _searchIndexClient;
private readonly ConcurrentDictionary<string, SearchClient> _clientsByIndex = new();

public AISearchClientManager(string endpoint, string apiKey, HttpClient? httpClient = null)
{
if (string.IsNullOrEmpty(endpoint))
throw new ArgumentNullException(nameof(endpoint));
if (string.IsNullOrEmpty(apiKey))
throw new ArgumentNullException(nameof(apiKey));

AzureKeyCredential credentials = new AzureKeyCredential(apiKey);

var options = new SearchClientOptions();
if (httpClient != null)
{
options.Transport = new HttpClientTransport(httpClient);
}

_searchIndexClient = new SearchIndexClient(new Uri(endpoint), credentials, options);
}

public SearchClient GetSearchClient(string indexName)
{
if (string.IsNullOrEmpty(indexName))
throw new ArgumentNullException(nameof(indexName));

if (!_clientsByIndex.TryGetValue(indexName, out SearchClient client))
{
client = _searchIndexClient.GetSearchClient(indexName);

_clientsByIndex[indexName] = client;
}

return client;
}
}
}
Loading

0 comments on commit 133d39a

Please sign in to comment.