Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
25 changes: 25 additions & 0 deletions tools/importdocument/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,33 @@ public sealed class Config
/// <summary>
/// Client ID for the app as registered in Azure AD.
/// </summary>
public string AuthenticationType { get; set; } = "None";

/// <summary>
/// Client ID for the import document tool as registered in Azure AD.
/// </summary>
public string ClientId { get; set; } = string.Empty;

/// <summary>
/// Client ID for the backend web api as registered in Azure AD.
/// </summary>
public string BackendClientId { get; set; } = string.Empty;

/// <summary>
/// Tenant ID against which to authenticate users in Azure AD.
/// </summary>
public string TenantId { get; set; } = string.Empty;

/// <summary>
/// Azure AD cloud instance for authenticating users.
/// </summary>
public string Instance { get; set; } = string.Empty;

/// <summary>
/// Scopes that the client app requires to access the API.
/// </summary>
public string Scopes { get; set; } = string.Empty;

/// <summary>
/// Redirect URI for the app as registered in Azure AD.
/// </summary>
Expand Down
52 changes: 21 additions & 31 deletions tools/importdocument/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,33 +59,23 @@ public static void Main(string[] args)
/// Acquires a user account from Azure AD.
/// </summary>
/// <param name="config">The App configuration.</param>
/// <param name="setAccount">Sets the account to the first account found.</param>
/// <param name="setAccessToken">Sets the access token to the first account found.</param>
/// <returns>True if the user account was acquired.</returns>
private static async Task<bool> AcquireUserAccountAsync(
private static async Task<bool> AcquireTokenAsync(
Config config,
Action<IAccount> setAccount,
Action<string> setAccessToken)
{
Console.WriteLine("Requesting User Account ID...");
Console.WriteLine("Attempting to authenticate user...");

string[] scopes = { "User.Read" };
var webApiScope = $"api://{config.BackendClientId}/{config.Scopes}";
string[] scopes = { webApiScope };
try
{
var app = PublicClientApplicationBuilder.Create(config.ClientId)
.WithRedirectUri(config.RedirectUri)
.WithAuthority(config.Instance, config.TenantId)
.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 false;
}

setAccount(first);
setAccessToken(result.AccessToken);
return true;
}
Expand Down Expand Up @@ -113,15 +103,16 @@ private static async Task ImportFilesAsync(IEnumerable<FileInfo> files, Config c
}
}

IAccount? userAccount = null;
string? accessToken = null;

if (await AcquireUserAccountAsync(config, v => { userAccount = v; }, v => { accessToken = v; }) == false)
if (config.AuthenticationType == "AzureAd")
{
Console.WriteLine("Error: Failed to acquire user account.");
return;
if (await AcquireTokenAsync(config, v => { accessToken = v; }) == false)
{
Console.WriteLine("Error: Failed to acquire access token.");
return;
}
Console.WriteLine($"Successfully acquired access token. Continuing...");
}
Console.WriteLine($"Successfully acquired User ID. Continuing...");

using var formContent = new MultipartFormDataContent();
List<StreamContent> filesContent = files.Select(file => new StreamContent(file.OpenRead())).ToList();
Expand All @@ -130,11 +121,6 @@ private static async Task ImportFilesAsync(IEnumerable<FileInfo> files, Config c
formContent.Add(filesContent[i], "formFiles", files.ElementAt(i).Name);
}

var userId = userAccount!.HomeAccountId.Identifier;
var userName = userAccount.Username;
using var userNameContent = new StringContent(userName);
formContent.Add(userNameContent, "userName");

if (chatCollectionId != Guid.Empty)
{
Console.WriteLine($"Uploading and parsing file to chat {chatCollectionId}...");
Expand All @@ -144,7 +130,7 @@ private static async Task ImportFilesAsync(IEnumerable<FileInfo> files, Config c
formContent.Add(chatCollectionIdContent, "chatId");

// Calling UploadAsync here to make sure disposable objects are still in scope.
await UploadAsync(formContent, accessToken!, config);
await UploadAsync(formContent, accessToken, config);
}
else
{
Expand All @@ -153,7 +139,7 @@ private static async Task ImportFilesAsync(IEnumerable<FileInfo> files, Config c
formContent.Add(globalScopeContent, "documentScope");

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

// Dispose of all the file streams.
Expand All @@ -170,7 +156,7 @@ private static async Task ImportFilesAsync(IEnumerable<FileInfo> files, Config c
/// <param name="config">Configuration.</param>
private static async Task UploadAsync(
MultipartFormDataContent multipartFormDataContent,
string accessToken,
string? accessToken,
Config config)
{
// Create a HttpClient instance and set the timeout to infinite since
Expand All @@ -183,8 +169,12 @@ private static async Task UploadAsync(
{
Timeout = Timeout.InfiniteTimeSpan
};
// Add required properties to the request header.
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {accessToken}");

if (config.AuthenticationType == "AzureAd")
{
// Add required properties to the request header.
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {accessToken!}");
}

try
{
Expand Down
83 changes: 66 additions & 17 deletions tools/importdocument/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,14 @@ relevant information from memories to provide more meaningful answers to users t
Memories can be generated from conversations as well as imported from external sources, such as documents.
Importing documents enables Copilot Chat to have up-to-date knowledge of specific contexts, such as enterprise and personal data.

## Configure your environment

1. A registered App in Azure Portal (https://learn.microsoft.com/azure/active-directory/develop/quickstart-register-app)
- Select Mobile and desktop applications as platform type, and the Redirect URI will be `http://localhost`
- Select **`Accounts in any organizational directory (Any Azure AD directory - Multitenant)
and personal Microsoft accounts (e.g. Skype, Xbox)`** as the supported account
type for this sample.
- Note the **`Application (client) ID`** from your app registration.
2. Make sure the service is running. To start the service, see [here](../../webapi/README.md).

## Running the app
## Running the app against a local Chat Copilot instance

1. Ensure the web api is running at `https://localhost:40443/`.
2. Configure the appsettings.json file under this folder root with the following variables and fill
in with your information, where
`ClientId` is the GUID copied from the **Application (client) ID** from your app registration in the Azure Portal,
`RedirectUri` is the Redirect URI also from the app registration in the Azure Portal, and
`ServiceUri` is the address the web api is running at.
2. Configure the appsettings.json file under this folder root with the following variables:
- `ServiceUri` is the address the web api is running at
- `AuthenticationType` should be set to "None"
- The remaining variables can be left blank or with their default values

3. Change directory to this folder root.
4. **Run** the following command to import a document to the app under the global document collection where
Expand All @@ -40,8 +30,6 @@ and personal Microsoft accounts (e.g. Skype, Xbox)`** as the supported account

`dotnet run --files .\sample-docs\ms10k.txt --chat-id [chatId]`

> Note that this will open a browser window for you to sign in to retrieve your user id to make sure you have access to the chat session.

> Currently only supports txt and pdf files. A sample file is provided under ./sample-docs.

Importing may take some time to generate embeddings for each piece/chunk of a document.
Expand All @@ -61,3 +49,64 @@ and personal Microsoft accounts (e.g. Skype, Xbox)`** as the supported account
With [Microsoft Responsible AI Standard v2 General Requirements.pdf](./sample-docs/Microsoft-Responsible-AI-Standard-v2-General-Requirements.pdf):

![Document-Memory-Sample-2](https://github.com/microsoft/chat-copilot/assets/52973358/f0e95104-72ca-4a0a-9555-ee335d8df696)


## Running the app against a deployed Chat Copilot instance

### Configure your environment

1. Create a registered app in Azure Portal (https://learn.microsoft.com/azure/active-directory/develop/quickstart-register-app).

> Note that this needs to be a separate app registration from those you created when deploying Chat Copilot.

- Select Mobile and desktop applications as platform type, and the Redirect URI will be `http://localhost`
- Select **`Accounts in this organizational directory only (Microsoft only - Single tenant)`** as the supported account
type for this sample.

> **IMPORTANT:** The supported account type should match that of the backend's app registration. If you changed this setting to allow allow multitenant and personal Microsoft accounts access to your Chat Copilot application, you should change it here as well.

- Note the **`Application (client) ID`** from your app registration.

2. Update the API permissions in the app registration you just created.

- From the left-hand menu, select **API permissions** and then **Add a permission**.
- Select **My APIs** and then select the application corresponding to your backend web api.
- Check the box next to `access_as_user` and then press **Add permissions**.

3. Update the authorized client applications for your backend web api.
- In the Azure portal, navigate to your backend web api's app registration.
- From the left-hand menu, select **Expose an API** and then **Add a client application**.
- Enter the client ID of the app registration you just created and check the box under **Authorized scopes**. Then press **Add application**.

4. Configure the appsettings.json file under this folder root with the following variables:
- `ServiceUri` is the address the web api is running at
- `AuthenticationType` should be set to "AzureAd"
- `ClientId` is the **Application (client) ID** GUID from the app registration you just created in the Azure Portal
- `RedirectUri` is the Redirect URI also from the app registration you just created in the Azure Portal (e.g. `http://localhost`)
- `BackendClientId` is the **Application (client) ID** GUID from your backend web api's app registration in the Azure Portal,
- `TenantId` is the Azure AD tenant ID that you want to authenticate users against. For single-tenant applications, this is the same as the **Directory (tenant) ID** from your app registration in the Azure Portal.
- `Instance` is the Azure AD cloud instance to authenticate users against. For most users, this is `https://login.microsoftonline.com`.
- `Scopes` should be set to "access_as_user"

### Run the app

1. Change directory to this folder root.
2. **Run** the following command to import a document to the app under the global document collection where
all users will have access to:

`dotnet run --files .\sample-docs\ms10k.txt`

Or **Run** the following command to import a document to the app under a chat isolated document collection where
only the chat session will have access to:

`dotnet run --files .\sample-docs\ms10k.txt --chat-id [chatId]`

> Note that both of these commands will open a browser window for you to sign in to ensure you have access to the Chat Copilot service.

> Currently only supports txt and pdf files. A sample file is provided under ./sample-docs.

Importing may take some time to generate embeddings for each piece/chunk of a document.

To import multiple files, specify multiple files. For example:

`dotnet run --files .\sample-docs\ms10k.txt .\sample-docs\Microsoft-Responsible-AI-Standard-v2-General-Requirements.pdf`
9 changes: 7 additions & 2 deletions tools/importdocument/appsettings.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
{
"Config": {
"ServiceUri": "https://localhost:40443",
"AuthenticationType": "None", // Supported authentication types are "None" or "AzureAd"
"ClientId": "",
"RedirectUri": "",
"ServiceUri": "https://localhost:40443"
"RedirectUri": "http://localhost",
"BackendClientId": "",
"TenantId": "",
"Instance": "https://login.microsoftonline.com",
"Scopes": "access_as_user" // Scopes that the client app requires to access the API
}
}