Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding file support #59

Merged
merged 18 commits into from
Aug 13, 2024
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
4 changes: 3 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
"image": "mcr.microsoft.com/devcontainers/base:jammy",
"features": {
"ghcr.io/devcontainers/features/dotnet:2": {},
"ghcr.io/devcontainers/features/node:1": {}
"ghcr.io/devcontainers/features/node:1": {},
"ghcr.io/devcontainers/features/python": "3.12",
"ghcr.io/devcontainers/features/azure-cli": "latest"
glecaros marked this conversation as resolved.
Show resolved Hide resolved
}

// Features to add to the dev container. More info: https://containers.dev/features.
Expand Down
3 changes: 2 additions & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
* @dargilco
* @glecaros
* @glecaros
* @rohit-ganguly
2 changes: 1 addition & 1 deletion samples/backend/csharp/ChatProtocolBackend.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<PackageReference Include="Azure.Identity" Version="1.11.4" />
<PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.6.0" />
<PackageReference Include="Microsoft.SemanticKernel" Version="1.7.1" />
<PackageReference Include="System.Text.Json" Version="8.0.3" />
<PackageReference Include="System.Text.Json" Version="8.0.4" />
</ItemGroup>

</Project>
76 changes: 75 additions & 1 deletion samples/backend/csharp/Controllers/ChatController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@

using Backend.Interfaces;
using Backend.Model;
using System.Text.RegularExpressions;

namespace Backend.Controllers;

[ApiController, Route("api/[controller]")]
public class ChatController : ControllerBase
public partial class ChatController : ControllerBase
{
private readonly ISemanticKernelApp _semanticKernelApp;

Expand All @@ -19,6 +20,79 @@ public ChatController(ISemanticKernelApp semanticKernelApp)
_semanticKernelApp = semanticKernelApp;
}

[GeneratedRegex(@"messages\[(\d+)\]\.files\[(\d+)\]")]
private static partial Regex MessageFilesRegex();

private (int MessageIndex, int FileIndex, IFormFile File) GetPosition(IFormFile formFile)
{
var match = MessageFilesRegex().Match(formFile.Name);
if (match.Success && int.TryParse(match.Groups[1].ValueSpan, out var messageIndex) && int.TryParse(match.Groups[2].ValueSpan, out var fileIndex))
{
return (messageIndex, fileIndex, formFile);
}

throw new ArgumentException("Malformed multipart request: Invalid file name.");
}

private async Task<AIChatRequest> RequestFromMultipart(IFormFileCollection formFiles)
{
using var jsonFileStream = formFiles
.First(f => f.Name == "json")
.OpenReadStream();
if (jsonFileStream is null)
{
throw new Exception("Malformed multipart request: Missing json part.");
}

var request = await JsonSerializer.DeserializeAsync<AIChatRequest>(jsonFileStream) ??
throw new Exception("Malformed multipart request: Invalid json part.");
foreach (var (messageIndex, fileIndex, file) in formFiles.Where(f => f.Name != "json").Select(GetPosition).OrderBy(p => p.MessageIndex).ThenBy(p => p.FileIndex))
{
if (request.Messages.Count <= messageIndex)
{
throw new Exception("Malformed multipart request: Invalid message index.");
}

var message = request.Messages[messageIndex];
message.Files ??= new List<AIChatFile>();
if (message.Files.Count != fileIndex)
{
throw new Exception("Malformed multipart request: Invalid file index.");
}

using var fileStream = file.OpenReadStream();
var fileData = await BinaryData.FromStreamAsync(fileStream);
message.Files.Add(new AIChatFile
{
ContentType = file.ContentType,
Data = fileData
});

}
glecaros marked this conversation as resolved.
Show resolved Hide resolved
return request;
}

[HttpPost]
[Consumes("multipart/form-data")]
public async Task<IActionResult> ProcessMessage(IFormFileCollection files)
{
try
{
var request = await RequestFromMultipart(files);
var session = request.SessionState switch
{
Guid sessionId => await _semanticKernelApp.GetSession(sessionId),
_ => await _semanticKernelApp.CreateSession(Guid.NewGuid())
glecaros marked this conversation as resolved.
Show resolved Hide resolved
};

return Ok(await session.ProcessRequest(request));
}
catch (Exception e)
{
return BadRequest(e.Message);
}
}

[HttpPost]
[Consumes("application/json")]
public async Task<IActionResult> ProcessMessage(AIChatRequest request)
Expand Down
15 changes: 15 additions & 0 deletions samples/backend/csharp/Model/AIChatFile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Text.Json.Serialization;

namespace Backend.Model;

public struct AIChatFile
{
[JsonPropertyName("contentType")]
public string ContentType { get; set; }

[JsonPropertyName("data")]
public BinaryData Data { get; set; }
}
3 changes: 3 additions & 0 deletions samples/backend/csharp/Model/AIChatMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,8 @@ public struct AIChatMessage

[JsonPropertyName("context")]
public BinaryData? Context { get; set; }

[JsonPropertyName("files")]
public IList<AIChatFile>? Files { get; set; }
}

13 changes: 7 additions & 6 deletions samples/backend/csharp/Services/SemanticKernelApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,15 @@ internal class SemanticKernelSession : ISemanticKernelSession
{
private readonly Kernel _kernel;
private readonly IStateStore<string> _stateStore;
private readonly KernelFunction _chatFunction;

public Guid Id { get; private set; }

internal SemanticKernelSession(Kernel kernel, IStateStore<string> stateStore, Guid sessionId)
{
_kernel = kernel;
_stateStore = stateStore;
_chatFunction = _kernel.CreateFunctionFromPrompt(prompt);
Id = sessionId;
}

Expand All @@ -66,16 +68,16 @@ ChatBot can have a conversation with you about any topic.
ChatBot:";

public async Task<AIChatCompletion> ProcessRequest(AIChatRequest message)
{
var chatFunction = _kernel.CreateFunctionFromPrompt(prompt);
{
var userInput = message.Messages.Last();
string history = await _stateStore.GetStateAsync(Id) ?? "";
/* TODO: Add support for text+image content */
var arguments = new KernelArguments()
{
["history"] = history,
["userInput"] = userInput.Content,
};
var botResponse = await chatFunction.InvokeAsync(_kernel, arguments);
var botResponse = await _chatFunction.InvokeAsync(_kernel, arguments);
var updatedHistory = $"{history}\nUser: {userInput.Content}\nChatBot: {botResponse}";
await _stateStore.SetStateAsync(Id, updatedHistory);
return new AIChatCompletion(Message: new AIChatMessage
Expand All @@ -89,16 +91,15 @@ public async Task<AIChatCompletion> ProcessRequest(AIChatRequest message)
}

public async IAsyncEnumerable<AIChatCompletionDelta> ProcessStreamingRequest(AIChatRequest message)
{
var chatFunction = _kernel.CreateFunctionFromPrompt(prompt);
{
var userInput = message.Messages.Last();
string history = await _stateStore.GetStateAsync(Id) ?? "";
var arguments = new KernelArguments()
{
["history"] = history,
["userInput"] = userInput.Content,
};
var streamedBotResponse = chatFunction.InvokeStreamingAsync(_kernel, arguments);
var streamedBotResponse = _chatFunction.InvokeStreamingAsync(_kernel, arguments);
StringBuilder response = new();
await foreach (var botResponse in streamedBotResponse)
{
Expand Down
1 change: 1 addition & 0 deletions samples/backend/js/expressjs/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
.env
dist
Loading