Skip to content

Commit

Permalink
Merge
Browse files Browse the repository at this point in the history
  • Loading branch information
crickman committed Aug 11, 2023
2 parents ab59840 + 7840959 commit 8ef45df
Show file tree
Hide file tree
Showing 23 changed files with 245 additions and 189 deletions.
61 changes: 30 additions & 31 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ name: "CodeQL"

on:
push:
branches: [ "main", "experimental*", "feature*" ]
branches: ["main", "experimental*", "feature*"]
schedule:
- cron: '17 11 * * 2'
- cron: "17 11 * * 2"

jobs:
analyze:
Expand All @@ -22,45 +22,44 @@ jobs:
strategy:
fail-fast: false
matrix:
language: [ 'csharp' ]
language: ["csharp", "javascript"]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Use only 'java' to analyze code written in Java, Kotlin or both
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support

steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Checkout repository
uses: actions/checkout@v3

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.

# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality

# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2

# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun

# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.

# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh

# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ These quick-start instructions run the sample locally. To deploy the sample to A
> **IMPORTANT:** Each chat interaction will call Azure OpenAI/OpenAI which will use tokens that you may be billed for.
![ChatCopilot](https://github.com/microsoft/chat-copilot/assets/64985898/4b5b4ddd-0ba5-4da1-9769-1bc4a74f1996)
![Chat Copilot answering a question](https://learn.microsoft.com/en-us/semantic-kernel/media/chat-copilot-in-action.gif)

# Requirements

Expand Down Expand Up @@ -141,7 +141,8 @@ You will need the following items to run the sample:
## (Optional) Enable backend authorization via Azure AD
1. Ensure you created the required application registration mentioned in [Start the WebApp FrontEnd application](#start-the-webapp-frontend-application)
1. Ensure you created the required application registration mentioned in [Register an application](#register-an-application)
2. Create a second application registration to represent the web api
> For more details on creating an application registration, go [here](https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app).
Expand Down
12 changes: 6 additions & 6 deletions scripts/deploy/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -227,27 +227,27 @@ resource appServiceWebConfig 'Microsoft.Web/sites/config@2022-09-01' = {
value: deployCosmosDB ? cosmosAccount.listConnectionStrings().connectionStrings[0].connectionString : ''
}
{
name: 'MemoriesStore:Type'
name: 'MemoryStore:Type'
value: memoryStore
}
{
name: 'MemoriesStore:Qdrant:Host'
name: 'MemoryStore:Qdrant:Host'
value: memoryStore == 'Qdrant' ? 'https://${appServiceQdrant.properties.defaultHostName}' : ''
}
{
name: 'MemoriesStore:Qdrant:Port'
name: 'MemoryStore:Qdrant:Port'
value: '443'
}
{
name: 'MemoriesStore:AzureCognitiveSearch:UseVectorSearch'
name: 'MemoryStore:AzureCognitiveSearch:UseVectorSearch'
value: 'true'
}
{
name: 'MemoriesStore:AzureCognitiveSearch:Endpoint'
name: 'MemoryStore:AzureCognitiveSearch:Endpoint'
value: memoryStore == 'AzureCognitiveSearch' ? 'https://${azureCognitiveSearch.name}.search.windows.net' : ''
}
{
name: 'MemoriesStore:AzureCognitiveSearch:Key'
name: 'MemoryStore:AzureCognitiveSearch:Key'
value: memoryStore == 'AzureCognitiveSearch' ? azureCognitiveSearch.listAdminKeys().primaryKey : ''
}
{
Expand Down
12 changes: 6 additions & 6 deletions scripts/deploy/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -336,27 +336,27 @@
"value": "[if(parameters('deployCosmosDB'), listConnectionStrings(resourceId('Microsoft.DocumentDB/databaseAccounts', toLower(format('cosmos-{0}', variables('uniqueName')))), '2023-04-15').connectionStrings[0].connectionString, '')]"
},
{
"name": "MemoriesStore:Type",
"name": "MemoryStore:Type",
"value": "[parameters('memoryStore')]"
},
{
"name": "MemoriesStore:Qdrant:Host",
"name": "MemoryStore:Qdrant:Host",
"value": "[if(equals(parameters('memoryStore'), 'Qdrant'), format('https://{0}', reference(resourceId('Microsoft.Web/sites', format('app-{0}-qdrant', variables('uniqueName'))), '2022-09-01').defaultHostName), '')]"
},
{
"name": "MemoriesStore:Qdrant:Port",
"name": "MemoryStore:Qdrant:Port",
"value": "443"
},
{
"name": "MemoriesStore:AzureCognitiveSearch:UseVectorSearch",
"name": "MemoryStore:AzureCognitiveSearch:UseVectorSearch",
"value": "true"
},
{
"name": "MemoriesStore:AzureCognitiveSearch:Endpoint",
"name": "MemoryStore:AzureCognitiveSearch:Endpoint",
"value": "[if(equals(parameters('memoryStore'), 'AzureCognitiveSearch'), format('https://{0}.search.windows.net', format('acs-{0}', variables('uniqueName'))), '')]"
},
{
"name": "MemoriesStore:AzureCognitiveSearch:Key",
"name": "MemoryStore:AzureCognitiveSearch:Key",
"value": "[if(equals(parameters('memoryStore'), 'AzureCognitiveSearch'), listAdminKeys(resourceId('Microsoft.Search/searchServices', format('acs-{0}', variables('uniqueName'))), '2022-09-01').primaryKey, '')]"
},
{
Expand Down
34 changes: 13 additions & 21 deletions webapi/Controllers/ChatMemoryController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,26 +60,31 @@ public async Task<IActionResult> GetSemanticMemoriesAsync(
[FromRoute] string chatId,
[FromRoute] string memoryName)
{
// Sanitize the log input by removing new line characters.
// https://github.com/microsoft/chat-copilot/security/code-scanning/1
var sanitizedChatId = chatId.Replace(Environment.NewLine, string.Empty, StringComparison.Ordinal);
var sanitizedMemoryName = memoryName.Replace(Environment.NewLine, string.Empty, StringComparison.Ordinal);

// Make sure the chat session exists.
if (!await this._chatSessionRepository.TryFindByIdAsync(chatId, v => _ = v))
{
this._logger.LogWarning("Chat session: {0} does not exist.", this.SanitizeLogInput(chatId));
return this.BadRequest($"Chat session: {chatId} does not exist.");
this._logger.LogWarning("Chat session: {0} does not exist.", sanitizedChatId);
return this.BadRequest($"Chat session: {sanitizedChatId} does not exist.");
}

// Make sure the memory name is valid.
if (!this.ValidateMemoryName(memoryName))
if (!this.ValidateMemoryName(sanitizedMemoryName))
{
this._logger.LogWarning("Memory name: {0} is invalid.", this.SanitizeLogInput(memoryName));
return this.BadRequest($"Memory name: {memoryName} is invalid.");
this._logger.LogWarning("Memory name: {0} is invalid.", sanitizedMemoryName);
return this.BadRequest($"Memory name: {sanitizedMemoryName} is invalid.");
}

// Gather the requested semantic memory.
// ISemanticTextMemory doesn't support retrieving all memories.
// Will use a dummy query since we don't care about relevance. An empty string will cause exception.
// minRelevanceScore is set to 0.0 to return all memories.
List<string> memories = new();
string memoryCollectionName = SemanticChatMemoryExtractor.MemoryCollectionName(chatId, memoryName);
string memoryCollectionName = SemanticChatMemoryExtractor.MemoryCollectionName(sanitizedChatId, sanitizedMemoryName);
try
{
var results = semanticTextMemory.SearchAsync(
Expand All @@ -95,7 +100,8 @@ public async Task<IActionResult> GetSemanticMemoriesAsync(
catch (SKException connectorException)
{
// A store exception might be thrown if the collection does not exist, depending on the memory store connector.
this._logger.LogError(connectorException, "Cannot search collection {0}", this.SanitizeLogInput(memoryCollectionName));
var sanitizedMemoryCollectionName = memoryCollectionName.Replace(Environment.NewLine, string.Empty, StringComparison.Ordinal);
this._logger.LogError(connectorException, "Cannot search collection {0}", sanitizedMemoryCollectionName);
}

return this.Ok(memories);
Expand All @@ -113,19 +119,5 @@ private bool ValidateMemoryName(string memoryName)
return this._promptOptions.MemoryMap.ContainsKey(memoryName);
}

/// <summary>
/// Sanitizes the log input by removing new line characters.
/// This helps prevent log forgery attacks from malicious text.
/// </summary>
/// <remarks>
/// https://github.com/microsoft/chat-copilot/security/code-scanning/1
/// </remarks>
/// <param name="input">The input to sanitize.</param>
/// <returns>The sanitized input.</returns>
private string SanitizeLogInput(string input)
{
return input.Replace(Environment.NewLine, string.Empty, StringComparison.Ordinal);
}

# endregion
}
12 changes: 6 additions & 6 deletions webapi/Controllers/ServiceOptionsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ public class ServiceOptionsController : ControllerBase
{
private readonly ILogger<ServiceOptionsController> _logger;

private readonly MemoriesStoreOptions _memoriesStoreOptions;
private readonly MemoryStoreOptions _memoryStoreOptions;

public ServiceOptionsController(
ILogger<ServiceOptionsController> logger,
IOptions<MemoriesStoreOptions> memoriesStoreOptions)
IOptions<MemoryStoreOptions> memoryStoreOptions)
{
this._logger = logger;
this._memoriesStoreOptions = memoriesStoreOptions.Value;
this._memoryStoreOptions = memoryStoreOptions.Value;
}

// TODO: [Issue #95] Include all service options in a single response.
Expand All @@ -42,10 +42,10 @@ public IActionResult GetServiceOptions()
return this.Ok(
new ServiceOptionsResponse()
{
MemoriesStore = new MemoriesStoreOptionResponse()
MemoryStore = new MemoryStoreOptionResponse()
{
Types = Enum.GetNames(typeof(MemoriesStoreOptions.MemoriesStoreType)),
SelectedType = this._memoriesStoreOptions.Type.ToString()
Types = Enum.GetNames(typeof(MemoryStoreOptions.MemoryStoreType)),
SelectedType = this._memoryStoreOptions.Type.ToString()
}
}
);
Expand Down
20 changes: 10 additions & 10 deletions webapi/Extensions/SemanticKernelExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
using Microsoft.SemanticKernel.Orchestration;
using Microsoft.SemanticKernel.Skills.Core;
using Microsoft.SemanticKernel.TemplateEngine;
using static CopilotChat.WebApi.Options.MemoriesStoreOptions;
using static CopilotChat.WebApi.Options.MemoryStoreOptions;

namespace CopilotChat.WebApi.Extensions;

Expand Down Expand Up @@ -155,18 +155,18 @@ private static Task RegisterSkillsAsync(IServiceProvider sp, IKernel kernel)
/// </summary>
private static void AddSemanticTextMemory(this IServiceCollection services)
{
MemoriesStoreOptions config = services.BuildServiceProvider().GetRequiredService<IOptions<MemoriesStoreOptions>>().Value;
MemoryStoreOptions config = services.BuildServiceProvider().GetRequiredService<IOptions<MemoryStoreOptions>>().Value;

switch (config.Type)
{
case MemoriesStoreType.Volatile:
case MemoryStoreType.Volatile:
services.AddSingleton<IMemoryStore, VolatileMemoryStore>();
break;

case MemoriesStoreType.Qdrant:
case MemoryStoreType.Qdrant:
if (config.Qdrant == null)
{
throw new InvalidOperationException("MemoriesStore type is Qdrant and Qdrant configuration is null.");
throw new InvalidOperationException("MemoryStore type is Qdrant and Qdrant configuration is null.");
}

services.AddSingleton<IMemoryStore>(sp =>
Expand All @@ -189,10 +189,10 @@ private static void AddSemanticTextMemory(this IServiceCollection services)
});
break;

case MemoriesStoreType.AzureCognitiveSearch:
case MemoryStoreType.AzureCognitiveSearch:
if (config.AzureCognitiveSearch == null)
{
throw new InvalidOperationException("MemoriesStore type is AzureCognitiveSearch and AzureCognitiveSearch configuration is null.");
throw new InvalidOperationException("MemoryStore type is AzureCognitiveSearch and AzureCognitiveSearch configuration is null.");
}

services.AddSingleton<IMemoryStore>(sp =>
Expand All @@ -201,10 +201,10 @@ private static void AddSemanticTextMemory(this IServiceCollection services)
});
break;

case MemoriesStoreOptions.MemoriesStoreType.Chroma:
case MemoryStoreOptions.MemoryStoreType.Chroma:
if (config.Chroma == null)
{
throw new InvalidOperationException("MemoriesStore type is Chroma and Chroma configuration is null.");
throw new InvalidOperationException("MemoryStore type is Chroma and Chroma configuration is null.");
}

services.AddSingleton<IMemoryStore>(sp =>
Expand All @@ -222,7 +222,7 @@ private static void AddSemanticTextMemory(this IServiceCollection services)
break;

default:
throw new InvalidOperationException($"Invalid 'MemoriesStore' type '{config.Type}'.");
throw new InvalidOperationException($"Invalid 'MemoryStore' type '{config.Type}'.");
}

services.AddScoped<ISemanticTextMemory>(sp => new SemanticTextMemory(
Expand Down
14 changes: 7 additions & 7 deletions webapi/Models/Response/ServiceOptionsResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,25 @@ namespace CopilotChat.WebApi.Models.Response;
public class ServiceOptionsResponse
{
/// <summary>
/// The memories store that is configured.
/// Configured memory store.
/// </summary>
[JsonPropertyName("memoriesStore")]
public MemoriesStoreOptionResponse MemoriesStore { get; set; } = new MemoriesStoreOptionResponse();
[JsonPropertyName("memoryStore")]
public MemoryStoreOptionResponse MemoryStore { get; set; } = new MemoryStoreOptionResponse();
}

/// <summary>
/// Response to memoriesStoreType request.
/// Response to memoryStoreType request.
/// </summary>
public class MemoriesStoreOptionResponse
public class MemoryStoreOptionResponse
{
/// <summary>
/// All the available memories store types.
/// All the available memory store types.
/// </summary>
[JsonPropertyName("types")]
public IEnumerable<string> Types { get; set; } = Enumerable.Empty<string>();

/// <summary>
/// The selected memories store type.
/// The selected memory store type.
/// </summary>
[JsonPropertyName("selectedType")]
public string SelectedType { get; set; } = string.Empty;
Expand Down
Loading

0 comments on commit 8ef45df

Please sign in to comment.