Skip to content

Commit

Permalink
[C#] bump: Migrate to OpenAI's official .NET SDK (#1858)
Browse files Browse the repository at this point in the history
## Linked issues

closes: #1821  (issue number)

## Details
* Migrated from `Azure.AI.OpenAI` to openai's official .NET sdk
`OpenAI`.

#### Change details
* **[BREAKING**] Removed `/completions` completions endpoint support for
both OpenAI & AzureOpenAI. It was deprecated by OpenAI June 2023 and
virtually no one uses it.
  * [x] Updated all the samples to migrate to `/chat` endpoint.
* Updated default AzureOpenAI api version to `2024-06-01`.
* The earliest supported api version is now `2024-04-01-preview`. This
is a consequence of using the openai's dotnet sdk `OpenAI`.
* Migrated `OpenAIModel` & `OpenAIEmbeddings` class' underlying clients.

## Attestation Checklist

- [x] My code follows the style guidelines of this project

- I have checked for/fixed spelling, linting, and other errors
- I have commented my code for clarity
- I have made corresponding changes to the documentation (updating the
doc strings in the code is sufficient)
- My changes generate no new warnings
- I have added tests that validates my changes, and provides sufficient
test coverage. I have tested with:
  - Local testing
  - E2E testing in Teams
- New and existing unit tests pass locally with my changes

### Additional information

> Feel free to add other relevant information below
  • Loading branch information
singhk97 authored Jul 25, 2024
1 parent 4a43c5f commit 2d49a75
Show file tree
Hide file tree
Showing 42 changed files with 913 additions and 862 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
using Microsoft.Teams.AI.AI.Models;
using Microsoft.Teams.AI.Exceptions;
using OpenAI.Chat;
using System.ClientModel.Primitives;
using ChatMessage = Microsoft.Teams.AI.AI.Models.ChatMessage;

namespace Microsoft.Teams.AI.Tests.AITests
{
Expand Down Expand Up @@ -28,5 +32,224 @@ public void Test_Get_Content_TypeMismatch_ThrowsException()
// Act & Assert
Assert.Throws<InvalidCastException>(() => msg.GetContent<bool>());
}

[Fact]
public void Test_Initialization_From_OpenAISdk_ChatMessage()
{
// Arrange
var chatCompletion = ModelReaderWriter.Read<ChatCompletion>(BinaryData.FromString(@$"{{
""choices"": [
{{
""finish_reason"": ""stop"",
""message"": {{
""role"": ""assistant"",
""content"": ""test-choice"",
""context"": {{
""citations"": [
{{
""title"": ""test-title"",
""url"": ""test-url"",
""content"": ""test-content""
}}
]
}}
}}
}}
]
}}"));

// Act
var message = new ChatMessage(chatCompletion!);

// Assert
Assert.Equal("test-choice", message.Content);
Assert.Equal(ChatRole.Assistant, message.Role);

var context = message.Context;
Assert.NotNull(context);
Assert.Equal(1, context.Citations.Count);
Assert.Equal("test-title", context.Citations[0].Title);
Assert.Equal("test-url", context.Citations[0].Url);
Assert.Equal("test-content", context.Citations[0].Content);
}

[Fact]
public void Test_InvalidRole_ToOpenAISdkChatMessage()
{
// Arrange
var chatMessage = new ChatMessage(new ChatRole("InvalidRole"))
{
Content = "test"
};

// Act
var ex = Assert.Throws<TeamsAIException>(() => chatMessage.ToOpenAIChatMessage());

// Assert
Assert.Equal($"Invalid chat message role: InvalidRole", ex.Message);
}

[Fact]
public void Test_UserRole_StringContent_ToOpenAISdkChatMessage()
{
// Arrange
var chatMessage = new ChatMessage(ChatRole.User)
{
Content = "test-content",
Name = "author"
};

// Act
var result = chatMessage.ToOpenAIChatMessage();

// Assert
var userMessage = result as UserChatMessage;
Assert.NotNull(userMessage);
Assert.Equal("test-content", result.Content[0].Text);
// TODO: Uncomment once participant name issue is resolved.
//Assert.Equal("author", userMessage.ParticipantName);
}

[Fact]
public void Test_UserRole_MultiModalContent_ToOpenAISdkChatMessage()
{
// Arrange
var messageContentParts = new List<MessageContentParts>() { new TextContentPart() { Text = "test" }, new ImageContentPart { ImageUrl = "https://www.testurl.com" } };
var chatMessage = new ChatMessage(ChatRole.User)
{
Content = messageContentParts,
Name = "author"
};

// Act
var result = chatMessage.ToOpenAIChatMessage();

// Assert
var userMessage = result as UserChatMessage;
Assert.NotNull(userMessage);
Assert.Equal("test", userMessage.Content[0].Text);
Assert.Equal("https://www.testurl.com", userMessage.Content[1].ImageUri.OriginalString);

// TODO: Uncomment once participant name issue is resolved.
//Assert.Equal("author", userMessage.ParticipantName);
}

[Fact]
public void Test_AssistantRole_ToOpenAISdkChatMessage_FunctionCall()
{
// Arrange
var functionCall = new FunctionCall("test-name", "test-arg1");
var chatMessage = new ChatMessage(ChatRole.Assistant)
{
Content = "test-content",
Name = "test-name",
FunctionCall = functionCall,
};

// Act
var result = chatMessage.ToOpenAIChatMessage();

// Assert
var assistantMessage = result as AssistantChatMessage;
Assert.NotNull(assistantMessage);
Assert.Equal("test-content", assistantMessage.Content[0].Text);
// TODO: Uncomment when participant name issue is resolved.
//Assert.Equal("test-name", assistantMessage.ParticipantName);
Assert.Equal("test-arg1", assistantMessage.FunctionCall.FunctionArguments);
Assert.Equal("test-name", assistantMessage.FunctionCall.FunctionName);
}

[Fact]
public void Test_AssistantRole_ToOpenAISdkChatMessage_ToolCall()
{
// Arrange
var chatMessage = new ChatMessage(ChatRole.Assistant)
{
Content = "test-content",
Name = "test-name",
ToolCalls = new List<ChatCompletionsToolCall>()
{
new ChatCompletionsFunctionToolCall("test-id", "test-tool-name", "test-tool-arg1")
}
};

// Act
var result = chatMessage.ToOpenAIChatMessage();

// Assert
var assistantMessage = result as AssistantChatMessage;
Assert.NotNull(assistantMessage);
Assert.Equal("test-content", assistantMessage.Content[0].Text);
// TODO: Uncomment when participant name issue is resolved.
//Assert.Equal("test-name", assistantMessage.ParticipantName);

Assert.Equal(1, assistantMessage.ToolCalls.Count);
ChatToolCall toolCall = assistantMessage.ToolCalls[0];
Assert.NotNull(toolCall);
Assert.Equal("test-id", toolCall.Id);
Assert.Equal("test-tool-name", toolCall.FunctionName);
Assert.Equal("test-tool-arg1", toolCall.FunctionArguments);
}

[Fact]
public void Test_SystemRole_ToOpenAISdkChatMessage()
{
// Arrange
var chatMessage = new ChatMessage(ChatRole.System)
{
Content = "test-content",
Name = "author"
};

// Act
var result = chatMessage.ToOpenAIChatMessage();

// Assert
var systemMessage = result as SystemChatMessage;
Assert.NotNull(systemMessage);
Assert.Equal("test-content", systemMessage.Content[0].Text);
// TODO: Uncomment when participant name issue is resolved.
//Assert.Equal("author", systemMessage.ParticipantName);
}

[Fact]
public void Test_FunctionRole_ToOpenAISdkChatMessage()
{
// Arrange
var chatMessage = new ChatMessage(ChatRole.Function)
{
Content = "test-content",
Name = "function-name"
};

// Act
var result = chatMessage.ToOpenAIChatMessage();

// Assert
var functionMessage = result as FunctionChatMessage;

Check warning on line 229 in dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ChatMessageTests.cs

View workflow job for this annotation

GitHub Actions / Analyze

'FunctionChatMessage' is obsolete: 'This field is marked as deprecated.'

Check warning on line 229 in dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ChatMessageTests.cs

View workflow job for this annotation

GitHub Actions / Analyze

'FunctionChatMessage' is obsolete: 'This field is marked as deprecated.'

Check warning on line 229 in dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ChatMessageTests.cs

View workflow job for this annotation

GitHub Actions / Build/Test/Lint (6.0)

'FunctionChatMessage' is obsolete: 'This field is marked as deprecated.'

Check warning on line 229 in dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ChatMessageTests.cs

View workflow job for this annotation

GitHub Actions / Build/Test/Lint (6.0)

'FunctionChatMessage' is obsolete: 'This field is marked as deprecated.'

Check warning on line 229 in dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ChatMessageTests.cs

View workflow job for this annotation

GitHub Actions / Build/Test/Lint (7.0)

'FunctionChatMessage' is obsolete: 'This field is marked as deprecated.'

Check warning on line 229 in dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ChatMessageTests.cs

View workflow job for this annotation

GitHub Actions / Build/Test/Lint (7.0)

'FunctionChatMessage' is obsolete: 'This field is marked as deprecated.'

Check warning on line 229 in dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ChatMessageTests.cs

View workflow job for this annotation

GitHub Actions / Build/Test/Lint / Build/Test/Lint (6.0)

'FunctionChatMessage' is obsolete: 'This field is marked as deprecated.'

Check warning on line 229 in dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ChatMessageTests.cs

View workflow job for this annotation

GitHub Actions / Build/Test/Lint / Build/Test/Lint (6.0)

'FunctionChatMessage' is obsolete: 'This field is marked as deprecated.'

Check warning on line 229 in dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ChatMessageTests.cs

View workflow job for this annotation

GitHub Actions / Build/Test/Lint / Build/Test/Lint (7.0)

'FunctionChatMessage' is obsolete: 'This field is marked as deprecated.'

Check warning on line 229 in dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/ChatMessageTests.cs

View workflow job for this annotation

GitHub Actions / Build/Test/Lint / Build/Test/Lint (7.0)

'FunctionChatMessage' is obsolete: 'This field is marked as deprecated.'
Assert.NotNull(functionMessage);
Assert.Equal("test-content", functionMessage.Content[0].Text);
}

[Fact]
public void Test_ToolRole_ToOpenAISdkChatMessage()
{
// Arrange
var chatMessage = new ChatMessage(ChatRole.Tool)
{
Content = "test-content",
Name = "tool-name",
ToolCallId = "tool-call-id"
};

// Act
var result = chatMessage.ToOpenAIChatMessage();

// Assert
var toolMessage = result as ToolChatMessage;
Assert.NotNull(toolMessage);
Assert.Equal("test-content", toolMessage.Content[0].Text);
Assert.Equal("tool-call-id", toolMessage.ToolCallId);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
using Microsoft.Teams.AI.AI.Models;
using Microsoft.Teams.AI.Exceptions;
using OpenAI.Chat;

namespace Microsoft.Teams.AI.Tests.AITests.Models
{
public class AzureSdkChatMessageExtensions
internal class ChatCompletionToolCallTests

Check warning on line 7 in dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/ChatCompletionToolCallTests.cs

View workflow job for this annotation

GitHub Actions / Analyze

Type 'ChatCompletionToolCallTests' can be sealed because it has no subtypes in its containing assembly and is not externally visible (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1852)

Check warning on line 7 in dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/ChatCompletionToolCallTests.cs

View workflow job for this annotation

GitHub Actions / Analyze

Type 'ChatCompletionToolCallTests' can be sealed because it has no subtypes in its containing assembly and is not externally visible (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1852)

Check warning on line 7 in dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/ChatCompletionToolCallTests.cs

View workflow job for this annotation

GitHub Actions / Build/Test/Lint (6.0)

Type 'ChatCompletionToolCallTests' can be sealed because it has no subtypes in its containing assembly and is not externally visible (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1852)

Check warning on line 7 in dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/ChatCompletionToolCallTests.cs

View workflow job for this annotation

GitHub Actions / Build/Test/Lint (6.0)

Type 'ChatCompletionToolCallTests' can be sealed because it has no subtypes in its containing assembly and is not externally visible (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1852)

Check warning on line 7 in dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/ChatCompletionToolCallTests.cs

View workflow job for this annotation

GitHub Actions / Build/Test/Lint (7.0)

Type 'ChatCompletionToolCallTests' can be sealed because it has no subtypes in its containing assembly and is not externally visible (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1852)

Check warning on line 7 in dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/ChatCompletionToolCallTests.cs

View workflow job for this annotation

GitHub Actions / Build/Test/Lint (7.0)

Type 'ChatCompletionToolCallTests' can be sealed because it has no subtypes in its containing assembly and is not externally visible (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1852)

Check warning on line 7 in dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/ChatCompletionToolCallTests.cs

View workflow job for this annotation

GitHub Actions / Build/Test/Lint / Build/Test/Lint (6.0)

Type 'ChatCompletionToolCallTests' can be sealed because it has no subtypes in its containing assembly and is not externally visible (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1852)

Check warning on line 7 in dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/ChatCompletionToolCallTests.cs

View workflow job for this annotation

GitHub Actions / Build/Test/Lint / Build/Test/Lint (6.0)

Type 'ChatCompletionToolCallTests' can be sealed because it has no subtypes in its containing assembly and is not externally visible (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1852)

Check warning on line 7 in dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/ChatCompletionToolCallTests.cs

View workflow job for this annotation

GitHub Actions / Build/Test/Lint / Build/Test/Lint (7.0)

Type 'ChatCompletionToolCallTests' can be sealed because it has no subtypes in its containing assembly and is not externally visible (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1852)

Check warning on line 7 in dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/AITests/Models/ChatCompletionToolCallTests.cs

View workflow job for this annotation

GitHub Actions / Build/Test/Lint / Build/Test/Lint (7.0)

Type 'ChatCompletionToolCallTests' can be sealed because it has no subtypes in its containing assembly and is not externally visible (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1852)
{
[Fact]
public void Test_ChatCompletionsToolCall_ToFunctionToolCall()
{
// Arrange
var functionToolCall = new Azure.AI.OpenAI.ChatCompletionsFunctionToolCall("test-id", "test-name", "test-arg1");
var functionToolCall = ChatToolCall.CreateFunctionToolCall("test-id", "test-name", "test-arg1");

// Act
var azureSdkFunctionToolCall = functionToolCall.ToChatCompletionsToolCall();
var azureSdkFunctionToolCall = ChatCompletionsToolCall.FromChatToolCall(functionToolCall);

// Assert
var toolCall = azureSdkFunctionToolCall as ChatCompletionsFunctionToolCall;
Expand All @@ -29,15 +30,15 @@ public void Test_ChatCompletionsToolCall_InvalidToolType()
var functionToolCall = new InvalidToolCall();

// Act
var ex = Assert.Throws<TeamsAIException>(() => functionToolCall.ToChatCompletionsToolCall());
var ex = Assert.Throws<TeamsAIException>(() => functionToolCall.ToChatToolCall());

// Assert
Assert.Equal($"Invalid ChatCompletionsToolCall type: {nameof(InvalidToolCall)}", ex.Message);
Assert.Equal("Invalid tool type: invalidToolType", ex.Message);
}

private sealed class InvalidToolCall : Azure.AI.OpenAI.ChatCompletionsToolCall
private sealed class InvalidToolCall : ChatCompletionsToolCall
{
public InvalidToolCall() : base("test-id")
public InvalidToolCall() : base("invalidToolType", "test-id")
{
}
}
Expand Down
Loading

0 comments on commit 2d49a75

Please sign in to comment.