Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
db3f02a
Remove auth
hathind-ms May 8, 2023
a2e4e60
Remove plugins
hathind-ms May 8, 2023
23c5889
Remove bot sharing
hathind-ms May 8, 2023
3a21075
Remove chat list
hathind-ms May 9, 2023
816ec8b
Remove planner
hathind-ms May 9, 2023
a1ea6f0
Remove Az speech and Bot export/import
hathind-ms May 9, 2023
e2f6052
Remove auth and deployment
hathind-ms May 9, 2023
7b59fa2
clean up
hathind-ms May 9, 2023
516ce7a
remove bot schema
hathind-ms May 9, 2023
64af155
Create chat only if it doesn't exist
hathind-ms May 9, 2023
57e78d5
Updates
hathind-ms May 9, 2023
e7c4251
Revert de-dup changes
hathind-ms May 9, 2023
0988367
Revert "Revert de-dup changes"
hathind-ms May 9, 2023
49fe097
Remove unused method
hathind-ms May 9, 2023
19ec828
update
hathind-ms May 9, 2023
f67b440
Updates
hathind-ms May 10, 2023
747947f
rename
hathind-ms May 10, 2023
79f9395
add folder
hathind-ms May 10, 2023
a3e09f5
add folder back
hathind-ms May 10, 2023
db32e00
Merge pull request #1 from hathind-ms/main
hathind-ms May 10, 2023
baeffe1
Fix message order
hathind-ms May 10, 2023
fa42b8d
Merge remote-tracking branch 'origin/feature-copilot-basic' into feat…
hathind-ms May 10, 2023
85c1869
updates
hathind-ms May 10, 2023
2baaf9a
Set default value for backend url
hathind-ms May 10, 2023
dfd06f3
Clean up
hathind-ms May 10, 2023
5b4e2b3
clean up
hathind-ms May 10, 2023
303d439
Update to OpenAI
hathind-ms May 10, 2023
d3fb718
update
hathind-ms May 10, 2023
a86fd8c
Delete images
hathind-ms May 10, 2023
d716e9e
Revert "Delete images"
hathind-ms May 10, 2023
70d2b89
Update model name for OpenAI
hathind-ms May 10, 2023
20d0453
Update ReadMe
hathind-ms May 10, 2023
91ad9c7
styling updates
hathind-ms May 10, 2023
1dcf8f4
Adding scripts
hathind-ms May 13, 2023
fcd85a5
add installation script
hathind-ms May 13, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions samples/apps/copilot-chat-app-basic/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Copilot Chat Sample Application
> This sample is for educational purposes only and is not recommended for production deployments.

# About Copilot Chat
This sample allows you to build your own integrated large language model chat copilot.
This is an enriched intelligence app, with multiple dynamic components including
command messages, user intent, and memories.

The chat prompt and response will evolve as the conversation between the user and the application proceeds.
This chat experience is orchestrated with Semantic Kernel and a Copilot Chat skill containing numerous
functions that work together to construct each response.

![UI Sample](images/UI-Sample.png)

# Configure your environment
Before you get started, make sure you have the following requirements in place:
- [.NET 6.0 SDK](https://dotnet.microsoft.com/download/dotnet/6.0)
- [Node.js](https://nodejs.org/)
- [Yarn](https://yarnpkg.com/)
- [Visual Studio Code](https://code.visualstudio.com/Download) **(Optional)**
- [Azure OpenAI](https://aka.ms/oai/access) resource or an account with [OpenAI](https://platform.openai.com).

Or try the installation scripts from here: [Install-Requirements](scripts/Install-Requirements.ps1)
# Start the app
You can run the app by running start scripts [Start.ps1](scripts/Start.ps1) or [Start.sh](scripts/Start.sh), or alternatively you can follow steps below to start webapi and webapp separately:
## Step-by-step instructions to run the webapi and webapp:
The sample uses two applications, a front-end web UI, and a back-end API server.
First, let’s set up and verify the back-end API server is running.

1. Generate and trust a localhost developer certificate. Open a terminal and run:
- For Windows and Mac run `dotnet dev-certs https --trust` and select `Yes` when asked if you want to install this certificate.
- For Linux run `dotnet dev-certs https`
> **Note:** It is recommended you close all instances of your web browser after installing the developer certificates.

1. Navigate to `samples/apps/copilot-chat-app/webapi` and open `appsettings.json`
- Update the `Completion`, `Embedding`, and `Planner:AIService` (if enabled) configuration sections:
- Update `AIService` to the AI service you will be using (i.e., `AzureOpenAI` or `OpenAI`).
- If your are using Azure OpenAI, update `Endpoint` to your Azure OpenAI resource Endpoint address (e.g.,
`http://contoso.openai.azure.com`).
> If you are using OpenAI, this property will be ignored.
- Set your Azure OpenAI key by opening a terminal in the webapi project directory and using `dotnet user-secrets`
```bash
cd semantic-kernel/samples/apps/copilot-chat-app/webapi
dotnet user-secrets set "Completion:Key" "MY_AZUREOPENAI_OR_OPENAI_KEY"
dotnet user-secrets set "Embedding:Key" "MY_AZUREOPENAI_OR_OPENAI_KEY"
```
- Update `DeploymentOrModelID` to the Azure OpenAI deployment or OpenAI models you want to use.
- For `Completion` and `Planner:AIService`, CopilotChat is optimized for Chat completion models, such as gpt-3.5-turbo and gpt-4.
> **Important:** gpt-3.5-turbo is normally labelled as "`gpt-35-turbo`" (no period) in Azure OpenAI and "`gpt-3.5-turbo`" (with a period) in OpenAI.
- For `Embedding`, `text-embedding-ada-002` is sufficient and cost-effect for generating embeddings.


1. Build and run the back-end API server
1. Open a terminal and navigate to `samples/apps/copilot-chat-app/webapi`

1. Run `dotnet build` to build the project.

1. Run `dotnet run` to start the server.

1. Verify the back-end server is responding, open a web browser and navigate to `https://localhost:40443/probe`
> The first time accessing the probe you may get a warning saying that there is a problem with website's certificate.
Select the option to accept/continue - this is expected when running a service on `localhost`
It is important to do this, as your browser may need to accept the certificate before allowing the frontend to communicate with the backend.

> You may also need to acknowledge the Windows Defender Firewall, and allow the app to communicate over private or public networks as appropriate.

1. Build and start the front-end application
1. To build and run the front-end application
```bash
yarn install
yarn start
```

1. With the back end and front end running, your web browser should automatically launch and navigate to `http://localhost:3000`
> The first time running the front-end application may take a minute or so to start.

1. Have fun!
> **Note:** Each chat interaction will call Azure OpenAI/OpenAI which will use tokens that you may be billed for.

## Troubleshooting
### 1. Localhost SSL certificate errors
![](images/Cert-Issue.png)

If you are stopped at an error message similar to the one above, your browser may be blocking the front-end access
to the back end while waiting for your permission to connect. To resolve this, try the following:

1. Confirm the backend service is running by opening a web browser, and navigating to `https://localhost:40443/probe`
- You should see a confirmation message: `Semantic Kernel service is up and running`
1. If your browser asks you to acknowledge the risks of visiting an insecurewebsite, you must acknowledge the
message before the front end will be allowed to connect to the back-end server.
- Acknowledge, continue, and navigate until you see the message Semantic Kernel service is up and running
1. Navigate to `https://localhost:3000` or refresh the page to use the Copilot Chat application.

### 2. Issues using text completion models, such as `text-davinci-003`
As of [PR #499](https://github.com/microsoft/semantic-kernel/pull/499), CopilotChat now focuses support on chat completion models, such as `gpt-3.5-*` and `gpt-4-*`
See [OpenAI's model compatiblity](https://platform.openai.com/docs/models/model-endpoint-compatibility) for
the complete list of current models supporting chat completions.

## Additional resources

1. [Import Document Application](./importdocument/README.md): Import a document to the memory store.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
53 changes: 53 additions & 0 deletions samples/apps/copilot-chat-app-basic/importdocument/Config.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright (c) Microsoft. All rights reserved.

using Microsoft.Extensions.Configuration;

namespace ImportDocument;

/// <summary>
/// Configuration for the app.
/// </summary>
public sealed class Config
{
/// <summary>
/// Client ID for the app as registered in Azure AD.
/// </summary>
public string ClientId { get; set; } = string.Empty;

/// <summary>
/// Redirect URI for the app as registered in Azure AD.
/// </summary>
#pragma warning disable CA1056 // URI-like properties should not be strings
public string RedirectUri { get; set; } = string.Empty;
#pragma warning restore CA1056 // URI-like properties should not be strings

/// <summary>
/// Uri for the service that is running the chat.
/// </summary>
#pragma warning disable CA1056 // URI-like properties should not be strings
public string ServiceUri { get; set; } = string.Empty;
#pragma warning restore CA1056 // URI-like properties should not be strings

/// <summary>
/// Gets configuration from appsettings.json.
/// </summary>
/// <returns>An Config instance</returns>
public static Config? GetConfig()
{
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();

return config.GetRequiredSection("Config").Get<Config>();
}

/// <summary>
/// Validates a Config object.
/// </summary>
/// <param name="config"></param>
/// <returns>True is the config object is not null.</returns>
public static bool Validate(Config? config)
{
return config != null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RepoRoot>$([System.IO.Path]::GetDirectoryName($([MSBuild]::GetPathOfFileAbove('.gitignore', '$(MSBuildThisFileDirectory)'))))</RepoRoot>
</PropertyGroup>
<Import Project="$(RepoRoot)/dotnet/Directory.Build.targets" />
<Import Project="$(RepoRoot)/dotnet/Directory.Build.props" />

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<Content Include="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0-preview.3.23174.8" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.0-preview.3.23174.8" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0-preview.3.23174.8" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.53.0" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
</ItemGroup>

</Project>
174 changes: 174 additions & 0 deletions samples/apps/copilot-chat-app-basic/importdocument/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Collections.Generic;
using System.CommandLine;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Identity.Client;

namespace ImportDocument;

/// <summary>
/// This console app imports a file to the CopilotChat WebAPI document memory store.
/// </summary>
public static class Program
{
public static void Main(string[] args)
{
var config = Config.GetConfig();
if (!Config.Validate(config))
{
Console.WriteLine("Error: Failed to read appsettings.json.");
return;
}

var fileOption = new Option<FileInfo>(name: "--file", description: "The file to import to document memory store.")
{
IsRequired = true
};

var userCollectionOption = new Option<bool>(
name: "--user-collection",
description: "Save the extracted context to an isolated user collection.",
getDefaultValue: () => false
);

var rootCommand = new RootCommand(
"This console app imports a file to the CopilotChat WebAPI's document memory store."
)
{
fileOption, userCollectionOption
};

rootCommand.SetHandler(async (file, userCollection) =>
{
await UploadFileAsync(file, config!, userCollection);
},
fileOption, userCollectionOption
);

rootCommand.Invoke(args);
}

/// <summary>
/// Acquires a user unique ID from Azure AD.
/// </summary>
private static async Task<string?> AcquireUserIdAsync(Config config)
{
Console.WriteLine("Requesting User Account ID...");

string[] scopes = { "User.Read" };
try
{
var app = PublicClientApplicationBuilder.Create(config.ClientId)
.WithRedirectUri(config.RedirectUri)
.Build();
var result = await app.AcquireTokenInteractive(scopes).ExecuteAsync();
IEnumerable<IAccount>? accounts = await app.GetAccountsAsync();
IAccount? first = accounts.FirstOrDefault();

if (first is null)
{
Console.WriteLine("Error: No accounts found");
return null;
}

return first.HomeAccountId.Identifier;
}
catch (Exception ex) when (ex is MsalServiceException or MsalClientException)
{
Console.WriteLine($"Error: {ex.Message}");
return null;
}
}

/// <summary>
/// Conditionally uploads a file to the Document Store for parsing.
/// </summary>
/// <param name="file">The file to upload for injection.</param>
/// <param name="config">Configuration.</param>
/// <param name="toUserCollection">Save the extracted context to an isolated user collection.</param>
private static async Task UploadFileAsync(FileInfo file, Config config, bool toUserCollection)
{
if (!file.Exists)
{
Console.WriteLine($"File {file.FullName} does not exist.");
return;
}

using var fileContent = new StreamContent(file.OpenRead());
using var formContent = new MultipartFormDataContent
{
{ fileContent, "formFile", file.Name }
};
if (toUserCollection)
{
Console.WriteLine("Uploading and parsing file to user collection...");
var userId = await AcquireUserIdAsync(config);

if (userId != null)
{
using var userScopeContent = new StringContent("User");
using var userIdContent = new StringContent(userId);
formContent.Add(userScopeContent, "documentScope");
formContent.Add(userIdContent, "userId");

// Calling UploadAsync here to make sure disposable objects are still in scope.
await UploadAsync(formContent, config);
}
}
else
{
Console.WriteLine("Uploading and parsing file to global collection...");
using var globalScopeContent = new StringContent("Global");
formContent.Add(globalScopeContent, "documentScope");

// Calling UploadAsync here to make sure disposable objects are still in scope.
await UploadAsync(formContent, config);
}
}

/// <summary>
/// Sends a POST request to the Document Store to upload a file for parsing.
/// </summary>
/// <param name="multipartFormDataContent">The multipart form data content to send.</param>
/// <param name="config">Configuration.</param>
private static async Task UploadAsync(MultipartFormDataContent multipartFormDataContent, Config config)
{
// Create a HttpClient instance and set the timeout to infinite since
// large documents will take a while to parse.
using HttpClientHandler clientHandler = new()
{
CheckCertificateRevocationList = true
};
using HttpClient httpClient = new(clientHandler)
{
Timeout = Timeout.InfiniteTimeSpan
};

try
{
using HttpResponseMessage response = await httpClient.PostAsync(
new Uri(new Uri(config.ServiceUri), "importDocument"),
multipartFormDataContent
);

if (!response.IsSuccessStatusCode)
{
Console.WriteLine($"Error: {response.StatusCode} {response.ReasonPhrase}");
Console.WriteLine(await response.Content.ReadAsStringAsync());
return;
}

Console.WriteLine("Uploading and parsing successful.");
}
catch (HttpRequestException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}
Loading