.Net: Fixed Azure Chat Completion with data example (#2663)

### Motivation and Context

<!-- Thank you for your contribution to the semantic-kernel repo!
Please help reviewers and future users, providing the following
information:
  1. Why is this change required?
  2. What problem does it solve?
  3. What scenario does it contribute to?
  4. If it fixes an open issue, please link to the issue here.
-->

Problem: 
In case of using `AzureChatCompletion` directly, `ChatHistory` object
should be maintained on developers' side (`AddUserMessage` ->
`AddAssistantMessage` -> `AddUserMessage`).

In case of `AzureChatCompletionWithData`, Azure OpenAI API is different,
so `ChatHistory` should also contain message from `Tool`, which is the
new chat role, that contains information from data source of chat
response. The flow of `ChatHistory` maintenance should look like this -
`AddUserMessage` -> `AddToolMessage` -> `AddAssistantMessage` ->
`AddUserMessage`).

More information here:

https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/use-your-data#conversation-history-for-better-results

This PR contains changes to provide `Tool` content as part of model
result and update code example to maintain chat history, when using
`AzureChatCompletionWithData` directly.

### Description

<!-- Describe your changes, the overall approach, the underlying design.
These notes will help understanding how your code works. Thanks! -->
1. Added `Tool` content as part of model result.
2. Updated code example to maintain chat history and improve console
output.

### Contribution Checklist

<!-- Before submitting this PR, please make sure: -->

- [x] The code builds clean without any errors or warnings
- [x] The PR follows the [SK Contribution
Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md)
and the [pre-submission formatting
script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts)
raises no violations
- [x] All unit tests pass, and I have added new tests where possible
- [x] I didn't break anyone 😄
This commit is contained in:
Dmytro Struk
2023-09-01 13:17:17 +00:00
committed by GitHub
parent e1595f4b30
commit 4d23e16d13
4 changed files with 74 additions and 18 deletions
@@ -34,25 +34,46 @@ public static class Example54_AzureChatCompletionWithData
var chatHistory = chatCompletion.CreateNewChat();
// First question without previous context based on uploaded content.
chatHistory.AddUserMessage("How did Emily and David meet?");
var ask = "How did Emily and David meet?";
chatHistory.AddUserMessage(ask);
// Chat Completion example
string reply = await chatCompletion.GenerateMessageAsync(chatHistory);
var chatResult = (await chatCompletion.GetChatCompletionsAsync(chatHistory))[0];
var chatMessage = await chatResult.GetChatMessageAsync();
// Output: Emily and David, both passionate scientists, met during a research expedition to Antarctica.
Console.WriteLine(reply);
var response = chatMessage.Content;
var toolResponse = chatResult.ModelResult.GetResult<ChatWithDataModelResult>().ToolContent;
// Output
// Ask: How did Emily and David meet?
// Response: Emily and David, both passionate scientists, met during a research expedition to Antarctica.
Console.WriteLine($"Ask: {ask}");
Console.WriteLine($"Response: {response}");
Console.WriteLine();
// Chat history maintenance
if (!string.IsNullOrEmpty(toolResponse))
{
chatHistory.AddMessage(AuthorRole.Tool, toolResponse);
}
chatHistory.AddAssistantMessage(response);
// Second question based on uploaded content.
chatHistory.AddUserMessage("What are Emily and David studying?");
ask = "What are Emily and David studying?";
chatHistory.AddUserMessage(ask);
// Chat Completion Streaming example
Console.WriteLine($"Ask: {ask}");
Console.WriteLine("Response: ");
await foreach (var result in chatCompletion.GetStreamingChatCompletionsAsync(chatHistory))
{
await foreach (var message in result.GetStreamingChatMessageAsync())
{
// Output:
// They are passionate scientists who study glaciology,
// Output
// Ask: What are Emily and David studying?
// Response: They are passionate scientists who study glaciology,
// a branch of geology that deals with the study of ice and its effects.
Console.Write(message.Content);
}
@@ -65,6 +86,8 @@ public static class Example54_AzureChatCompletionWithData
{
Console.WriteLine("=== Example with Kernel ===");
var ask = "How did Emily and David meet?";
var completionWithDataConfig = GetCompletionWithDataConfig();
IKernel kernel = new KernelBuilder()
@@ -74,21 +97,26 @@ public static class Example54_AzureChatCompletionWithData
var semanticFunction = kernel.CreateSemanticFunction("Question: {{$input}}");
// First question without previous context based on uploaded content.
var result = await kernel.RunAsync("How did Emily and David meet?", semanticFunction);
var response = await kernel.RunAsync(ask, semanticFunction);
// Output: Emily and David, both passionate scientists, met during a research expedition to Antarctica.
Console.WriteLine(result);
// Output
// Ask: How did Emily and David meet?
// Response: Emily and David, both passionate scientists, met during a research expedition to Antarctica.
Console.WriteLine($"Ask: {ask}");
Console.WriteLine($"Response: {response}");
Console.WriteLine();
// Second question based on uploaded content.
result = await kernel.RunAsync("What are Emily and David studying?", semanticFunction);
ask = "What are Emily and David studying?";
response = await kernel.RunAsync(ask, semanticFunction);
// Output:
// They are passionate scientists who study glaciology,
// Output
// Ask: What are Emily and David studying?
// Response: They are passionate scientists who study glaciology,
// a branch of geology that deals with the study of ice and its effects.
Console.WriteLine(result);
Console.WriteLine(Environment.NewLine);
Console.WriteLine($"Ask: {ask}");
Console.WriteLine($"Response: {response}");
Console.WriteLine();
}
/// <summary>
@@ -20,6 +20,12 @@ public class ChatWithDataModelResult
/// </summary>
public DateTimeOffset Created { get; }
/// <summary>
/// Content from data source, including citations.
/// For more information see <see href="https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/use-your-data#conversation-history-for-better-results"/>.
/// </summary>
public string? ToolContent { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="ChatWithDataModelResult"/> class.
/// </summary>
@@ -21,7 +21,10 @@ internal sealed class ChatWithDataResult : IChatResult, ITextResult
Verify.NotNull(response);
Verify.NotNull(choice);
this.ModelResult = new(new ChatWithDataModelResult(response.Id, DateTimeOffset.FromUnixTimeSeconds(response.Created)));
this.ModelResult = new(new ChatWithDataModelResult(response.Id, DateTimeOffset.FromUnixTimeSeconds(response.Created))
{
ToolContent = this.GetToolContent(choice)
});
this._choice = choice;
}
@@ -45,5 +48,13 @@ internal sealed class ChatWithDataResult : IChatResult, ITextResult
private readonly ChatWithDataChoice _choice;
private string? GetToolContent(ChatWithDataChoice choice)
{
var message = choice.Messages
.FirstOrDefault(message => message.Role.Equals(AuthorRole.Tool.Label, StringComparison.Ordinal));
return message?.Content;
}
#endregion
}
@@ -23,7 +23,10 @@ internal sealed class ChatWithDataStreamingResult : IChatStreamingResult, ITextS
Verify.NotNull(response);
Verify.NotNull(choice);
this.ModelResult = new(new ChatWithDataModelResult(response.Id, DateTimeOffset.FromUnixTimeSeconds(response.Created)));
this.ModelResult = new(new ChatWithDataModelResult(response.Id, DateTimeOffset.FromUnixTimeSeconds(response.Created))
{
ToolContent = this.GetToolContent(choice)
});
this._choice = choice;
}
@@ -72,5 +75,13 @@ internal sealed class ChatWithDataStreamingResult : IChatStreamingResult, ITextS
(message.Delta.Role is null || !message.Delta.Role.Equals(AuthorRole.Tool.Label, StringComparison.Ordinal));
}
private string? GetToolContent(ChatWithDataStreamingChoice choice)
{
var message = choice.Messages
.FirstOrDefault(message => message.Delta.Role is not null && message.Delta.Role.Equals(AuthorRole.Tool.Label, StringComparison.Ordinal));
return message?.Delta?.Content;
}
#endregion
}