From 9e59698d0d72f1aebfdff5c1ad046d9d6c864b93 Mon Sep 17 00:00:00 2001
From: Chris <66376200+crickman@users.noreply.github.com>
Date: Mon, 12 Aug 2024 07:42:22 -0700
Subject: [PATCH] .Net Agents - Assistant V2 Migration (#7126)
### Motivation and Context
Support Assistant V2 features according to
[ADR](https://github.com/microsoft/semantic-kernel/blob/adr_assistant_v2/docs/decisions/0049-agents-assistantsV2.md)
(based on V2 AI connector migration)
### Description
- Refactored `OpenAIAssistantAgent` to support all V2 options except:
streaming, message-attachment, tool_choice
- Streaming to be addressed as a separate change
- Extensive enhancement of unit-tests
- Migrated samples to use `FileClient`
- Deep pass to enhance and improve samples
- Reviewed and updated test-coverage, generally
### Contribution Checklist
- [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 :smile:
---------
Co-authored-by: Roger Barreto <19890735+RogerBarreto@users.noreply.github.com>
---
dotnet/Directory.Packages.props | 1 -
dotnet/SK-dotnet.sln | 50 +-
.../ChatCompletion_FunctionTermination.cs | 35 +-
.../Agents/ChatCompletion_Streaming.cs | 23 +-
.../Agents/ComplexChat_NestedShopper.cs | 18 +-
.../Concepts/Agents/Legacy_AgentAuthoring.cs | 12 +-
.../Concepts/Agents/Legacy_AgentCharts.cs | 48 +-
.../Agents/Legacy_AgentCollaboration.cs | 25 +-
.../Concepts/Agents/Legacy_AgentDelegation.cs | 16 +-
.../Concepts/Agents/Legacy_AgentTools.cs | 59 +-
.../samples/Concepts/Agents/Legacy_Agents.cs | 29 +-
.../Concepts/Agents/MixedChat_Agents.cs | 20 +-
.../Concepts/Agents/MixedChat_Files.cs | 53 +-
.../Concepts/Agents/MixedChat_Images.cs | 42 +-
.../Agents/OpenAIAssistant_ChartMaker.cs | 38 +-
.../OpenAIAssistant_FileManipulation.cs | 57 +-
.../Agents/OpenAIAssistant_FileService.cs | 4 +-
.../Agents/OpenAIAssistant_Retrieval.cs | 71 --
dotnet/samples/Concepts/Concepts.csproj | 10 +-
.../Resources/Plugins/LegacyMenuPlugin.cs | 25 -
.../Concepts/Resources/Plugins/MenuPlugin.cs | 34 -
.../GettingStartedWithAgents.csproj | 18 +-
.../GettingStartedWithAgents/README.md | 18 +-
.../Resources/cat.jpg | Bin 0 -> 37831 bytes
.../Resources/employees.pdf | Bin 0 -> 43422 bytes
.../{Step1_Agent.cs => Step01_Agent.cs} | 14 +-
.../{Step2_Plugins.cs => Step02_Plugins.cs} | 35 +-
.../{Step3_Chat.cs => Step03_Chat.cs} | 14 +-
....cs => Step04_KernelFunctionStrategies.cs} | 23 +-
...ep5_JsonResult.cs => Step05_JsonResult.cs} | 21 +-
...ction.cs => Step06_DependencyInjection.cs} | 49 +-
.../{Step7_Logging.cs => Step07_Logging.cs} | 16 +-
...OpenAIAssistant.cs => Step08_Assistant.cs} | 58 +-
.../Step09_Assistant_Vision.cs | 74 +++
.../Step10_AssistantTool_CodeInterpreter.cs} | 32 +-
.../Step11_AssistantTool_FileSearch.cs | 83 +++
.../src/Agents/Abstractions/AgentChannel.cs | 8 +
dotnet/src/Agents/Abstractions/AgentChat.cs | 2 +-
.../Agents/Abstractions/AggregatorChannel.cs | 3 +
.../Logging/AgentChatLogMessages.cs | 2 +-
dotnet/src/Agents/Core/ChatCompletionAgent.cs | 11 +-
.../ChatHistorySummarizationReducer.cs | 6 +-
dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj | 4 +-
.../OpenAI/Extensions/AuthorRoleExtensions.cs | 2 +-
.../Extensions/KernelFunctionExtensions.cs | 9 +-
.../AddHeaderRequestPolicy.cs | 2 +-
.../Internal/AssistantMessageFactory.cs | 64 ++
.../Internal/AssistantRunOptionsFactory.cs | 53 ++
.../{ => Internal}/AssistantThreadActions.cs | 203 +++---
.../Internal/AssistantToolResourcesFactory.cs | 51 ++
.../AssistantThreadActionsLogMessages.cs | 3 +-
.../src/Agents/OpenAI/OpenAIAssistantAgent.cs | 300 +++++----
.../Agents/OpenAI/OpenAIAssistantChannel.cs | 9 +-
.../OpenAI/OpenAIAssistantConfiguration.cs | 91 ---
.../OpenAI/OpenAIAssistantDefinition.cs | 71 +-
.../OpenAI/OpenAIAssistantExecutionOptions.cs | 38 ++
.../OpenAIAssistantInvocationOptions.cs | 88 +++
.../src/Agents/OpenAI/OpenAIClientProvider.cs | 173 +++++
.../OpenAI/OpenAIThreadCreationOptions.cs | 37 ++
dotnet/src/Agents/OpenAI/RunPollingOptions.cs | 57 ++
.../src/Agents/UnitTests/AgentChannelTests.cs | 27 +-
dotnet/src/Agents/UnitTests/AgentChatTests.cs | 60 +-
.../Agents/UnitTests/Agents.UnitTests.csproj | 3 +-
.../Agents/UnitTests/AggregatorAgentTests.cs | 24 +-
.../UnitTests/Core/AgentGroupChatTests.cs | 30 +
.../Core/Chat/AgentGroupChatSettingsTests.cs | 7 +
.../AggregatorTerminationStrategyTests.cs | 41 +-
.../KernelFunctionSelectionStrategyTests.cs | 54 +-
.../KernelFunctionTerminationStrategyTests.cs | 23 +-
.../Chat/RegExTerminationStrategyTests.cs | 20 +-
.../Chat/SequentialSelectionStrategyTests.cs | 38 +-
.../Core/ChatCompletionAgentTests.cs | 71 +-
.../UnitTests/Core/ChatHistoryChannelTests.cs | 22 +-
.../ChatHistoryReducerExtensionsTests.cs | 39 +-
.../ChatHistorySummarizationReducerTests.cs | 75 ++-
.../ChatHistoryTruncationReducerTests.cs | 49 +-
.../Extensions/ChatHistoryExtensionsTests.cs | 4 +
.../UnitTests/Internal/BroadcastQueueTests.cs | 31 +-
.../UnitTests/Internal/KeyEncoderTests.cs | 5 +-
dotnet/src/Agents/UnitTests/MockAgent.cs | 5 +-
.../UnitTests/OpenAI/AssertCollection.cs | 46 ++
.../Azure/AddHeaderRequestPolicyTests.cs | 7 +-
.../Extensions/AuthorRoleExtensionsTests.cs | 5 +-
.../Extensions/KernelExtensionsTests.cs | 6 +
.../KernelFunctionExtensionsTests.cs | 20 +-
.../Internal/AssistantMessageFactoryTests.cs | 210 ++++++
.../AssistantRunOptionsFactoryTests.cs | 139 ++++
.../OpenAI/OpenAIAssistantAgentTests.cs | 610 ++++++++++++++----
.../OpenAIAssistantConfigurationTests.cs | 61 --
.../OpenAI/OpenAIAssistantDefinitionTests.cs | 85 ++-
.../OpenAIAssistantInvocationOptionsTests.cs | 100 +++
.../OpenAI/OpenAIClientProviderTests.cs | 86 +++
.../OpenAIThreadCreationOptionsTests.cs | 75 +++
.../OpenAI/RunPollingOptionsTests.cs | 71 ++
.../Agents/Extensions/OpenAIRestExtensions.cs | 3 +-
.../Experimental/Agents/Internal/ChatRun.cs | 18 +-
.../Agents/ChatCompletionAgentTests.cs | 18 +-
.../Agents/OpenAIAssistantAgentTests.cs | 38 +-
.../samples/AgentUtilities/BaseAgentsTest.cs | 129 ++++
.../samples/SamplesInternalUtilities.props | 5 +-
.../Contents/AnnotationContent.cs | 2 +-
.../Contents/FileReferenceContent.cs | 2 +-
102 files changed, 3412 insertions(+), 1364 deletions(-)
delete mode 100644 dotnet/samples/Concepts/Agents/OpenAIAssistant_Retrieval.cs
delete mode 100644 dotnet/samples/Concepts/Resources/Plugins/MenuPlugin.cs
create mode 100644 dotnet/samples/GettingStartedWithAgents/Resources/cat.jpg
create mode 100644 dotnet/samples/GettingStartedWithAgents/Resources/employees.pdf
rename dotnet/samples/GettingStartedWithAgents/{Step1_Agent.cs => Step01_Agent.cs} (76%)
rename dotnet/samples/GettingStartedWithAgents/{Step2_Plugins.cs => Step02_Plugins.cs} (76%)
rename dotnet/samples/GettingStartedWithAgents/{Step3_Chat.cs => Step03_Chat.cs} (86%)
rename dotnet/samples/GettingStartedWithAgents/{Step4_KernelFunctionStrategies.cs => Step04_KernelFunctionStrategies.cs} (84%)
rename dotnet/samples/GettingStartedWithAgents/{Step5_JsonResult.cs => Step05_JsonResult.cs} (79%)
rename dotnet/samples/GettingStartedWithAgents/{Step6_DependencyInjection.cs => Step06_DependencyInjection.cs} (65%)
rename dotnet/samples/GettingStartedWithAgents/{Step7_Logging.cs => Step07_Logging.cs} (86%)
rename dotnet/samples/GettingStartedWithAgents/{Step8_OpenAIAssistant.cs => Step08_Assistant.cs} (57%)
create mode 100644 dotnet/samples/GettingStartedWithAgents/Step09_Assistant_Vision.cs
rename dotnet/samples/{Concepts/Agents/OpenAIAssistant_CodeInterpreter.cs => GettingStartedWithAgents/Step10_AssistantTool_CodeInterpreter.cs} (50%)
create mode 100644 dotnet/samples/GettingStartedWithAgents/Step11_AssistantTool_FileSearch.cs
rename dotnet/src/Agents/OpenAI/{Azure => Internal}/AddHeaderRequestPolicy.cs (87%)
create mode 100644 dotnet/src/Agents/OpenAI/Internal/AssistantMessageFactory.cs
create mode 100644 dotnet/src/Agents/OpenAI/Internal/AssistantRunOptionsFactory.cs
rename dotnet/src/Agents/OpenAI/{ => Internal}/AssistantThreadActions.cs (68%)
create mode 100644 dotnet/src/Agents/OpenAI/Internal/AssistantToolResourcesFactory.cs
delete mode 100644 dotnet/src/Agents/OpenAI/OpenAIAssistantConfiguration.cs
create mode 100644 dotnet/src/Agents/OpenAI/OpenAIAssistantExecutionOptions.cs
create mode 100644 dotnet/src/Agents/OpenAI/OpenAIAssistantInvocationOptions.cs
create mode 100644 dotnet/src/Agents/OpenAI/OpenAIClientProvider.cs
create mode 100644 dotnet/src/Agents/OpenAI/OpenAIThreadCreationOptions.cs
create mode 100644 dotnet/src/Agents/OpenAI/RunPollingOptions.cs
create mode 100644 dotnet/src/Agents/UnitTests/OpenAI/AssertCollection.cs
create mode 100644 dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantMessageFactoryTests.cs
create mode 100644 dotnet/src/Agents/UnitTests/OpenAI/Internal/AssistantRunOptionsFactoryTests.cs
delete mode 100644 dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantConfigurationTests.cs
create mode 100644 dotnet/src/Agents/UnitTests/OpenAI/OpenAIAssistantInvocationOptionsTests.cs
create mode 100644 dotnet/src/Agents/UnitTests/OpenAI/OpenAIClientProviderTests.cs
create mode 100644 dotnet/src/Agents/UnitTests/OpenAI/OpenAIThreadCreationOptionsTests.cs
create mode 100644 dotnet/src/Agents/UnitTests/OpenAI/RunPollingOptionsTests.cs
create mode 100644 dotnet/src/InternalUtilities/samples/AgentUtilities/BaseAgentsTest.cs
diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props
index 1b55be45d37d..2e15ff89460f 100644
--- a/dotnet/Directory.Packages.props
+++ b/dotnet/Directory.Packages.props
@@ -9,7 +9,6 @@
-
diff --git a/dotnet/SK-dotnet.sln b/dotnet/SK-dotnet.sln
index 4c4ed6c4df5a..b4580b4d1146 100644
--- a/dotnet/SK-dotnet.sln
+++ b/dotnet/SK-dotnet.sln
@@ -277,16 +277,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GettingStartedWithAgents",
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{77E141BA-AF5E-4C01-A970-6C07AC3CD55A}"
ProjectSection(SolutionItems) = preProject
- src\InternalUtilities\samples\ConfigurationNotFoundException.cs = src\InternalUtilities\samples\ConfigurationNotFoundException.cs
- src\InternalUtilities\samples\EnumerableExtensions.cs = src\InternalUtilities\samples\EnumerableExtensions.cs
- src\InternalUtilities\samples\Env.cs = src\InternalUtilities\samples\Env.cs
- src\InternalUtilities\samples\ObjectExtensions.cs = src\InternalUtilities\samples\ObjectExtensions.cs
- src\InternalUtilities\samples\PlanExtensions.cs = src\InternalUtilities\samples\PlanExtensions.cs
- src\InternalUtilities\samples\RepoFiles.cs = src\InternalUtilities\samples\RepoFiles.cs
src\InternalUtilities\samples\SamplesInternalUtilities.props = src\InternalUtilities\samples\SamplesInternalUtilities.props
- src\InternalUtilities\samples\TextOutputHelperExtensions.cs = src\InternalUtilities\samples\TextOutputHelperExtensions.cs
- src\InternalUtilities\samples\XunitLogger.cs = src\InternalUtilities\samples\XunitLogger.cs
- src\InternalUtilities\samples\YourAppException.cs = src\InternalUtilities\samples\YourAppException.cs
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Functions.Prompty", "src\Functions\Functions.Prompty\Functions.Prompty.csproj", "{12B06019-740B-466D-A9E0-F05BC123A47D}"
@@ -340,9 +331,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Connectors.Qdrant.UnitTests
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Connectors.Redis.UnitTests", "src\Connectors\Connectors.Redis.UnitTests\Connectors.Redis.UnitTests.csproj", "{ACD8C464-AEC9-45F6-A458-50A84F353DB7}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StepwisePlannerMigration", "samples\Demos\StepwisePlannerMigration\StepwisePlannerMigration.csproj", "{38374C62-0263-4FE8-A18C-70FC8132912B}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AIModelRouter", "samples\Demos\AIModelRouter\AIModelRouter.csproj", "{E06818E3-00A5-41AC-97ED-9491070CDEA1}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AIModelRouter", "samples\Demos\AIModelRouter\AIModelRouter.csproj", "{E06818E3-00A5-41AC-97ED-9491070CDEA1}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CreateChatGptPlugin", "CreateChatGptPlugin", "{F8B82F6B-B16A-4F8C-9C41-E9CD8D79A098}"
EndProject
@@ -352,6 +341,29 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MathPlugin", "MathPlugin",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "kernel-functions-generator", "samples\Demos\CreateChatGptPlugin\MathPlugin\kernel-functions-generator\kernel-functions-generator.csproj", "{4326A974-F027-4ABD-A220-382CC6BB0801}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utilities", "Utilities", "{EE454832-085F-4D37-B19B-F94F7FC6984A}"
+ ProjectSection(SolutionItems) = preProject
+ src\InternalUtilities\samples\InternalUtilities\BaseTest.cs = src\InternalUtilities\samples\InternalUtilities\BaseTest.cs
+ src\InternalUtilities\samples\InternalUtilities\ConfigurationNotFoundException.cs = src\InternalUtilities\samples\InternalUtilities\ConfigurationNotFoundException.cs
+ src\InternalUtilities\samples\InternalUtilities\EmbeddedResource.cs = src\InternalUtilities\samples\InternalUtilities\EmbeddedResource.cs
+ src\InternalUtilities\samples\InternalUtilities\EnumerableExtensions.cs = src\InternalUtilities\samples\InternalUtilities\EnumerableExtensions.cs
+ src\InternalUtilities\samples\InternalUtilities\Env.cs = src\InternalUtilities\samples\InternalUtilities\Env.cs
+ src\InternalUtilities\samples\InternalUtilities\JsonResultTranslator.cs = src\InternalUtilities\samples\InternalUtilities\JsonResultTranslator.cs
+ src\InternalUtilities\samples\InternalUtilities\ObjectExtensions.cs = src\InternalUtilities\samples\InternalUtilities\ObjectExtensions.cs
+ src\InternalUtilities\samples\InternalUtilities\RepoFiles.cs = src\InternalUtilities\samples\InternalUtilities\RepoFiles.cs
+ src\InternalUtilities\samples\InternalUtilities\TestConfiguration.cs = src\InternalUtilities\samples\InternalUtilities\TestConfiguration.cs
+ src\InternalUtilities\samples\InternalUtilities\TextOutputHelperExtensions.cs = src\InternalUtilities\samples\InternalUtilities\TextOutputHelperExtensions.cs
+ src\InternalUtilities\samples\InternalUtilities\XunitLogger.cs = src\InternalUtilities\samples\InternalUtilities\XunitLogger.cs
+ src\InternalUtilities\samples\InternalUtilities\YourAppException.cs = src\InternalUtilities\samples\InternalUtilities\YourAppException.cs
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Agents", "Agents", "{5C6C30E0-7AC1-47F4-8244-57B066B43FD8}"
+ ProjectSection(SolutionItems) = preProject
+ src\InternalUtilities\samples\AgentUtilities\BaseAgentsTest.cs = src\InternalUtilities\samples\AgentUtilities\BaseAgentsTest.cs
+ EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StepwisePlannerMigration", "samples\Demos\StepwisePlannerMigration\StepwisePlannerMigration.csproj", "{2A6B056D-B35A-4CCE-80EE-0307EA9C3A57}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -839,10 +851,6 @@ Global
{ACD8C464-AEC9-45F6-A458-50A84F353DB7}.Publish|Any CPU.Build.0 = Debug|Any CPU
{ACD8C464-AEC9-45F6-A458-50A84F353DB7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ACD8C464-AEC9-45F6-A458-50A84F353DB7}.Release|Any CPU.Build.0 = Release|Any CPU
- {38374C62-0263-4FE8-A18C-70FC8132912B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {38374C62-0263-4FE8-A18C-70FC8132912B}.Publish|Any CPU.ActiveCfg = Debug|Any CPU
- {38374C62-0263-4FE8-A18C-70FC8132912B}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {38374C62-0263-4FE8-A18C-70FC8132912B}.Release|Any CPU.Build.0 = Release|Any CPU
{E06818E3-00A5-41AC-97ED-9491070CDEA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E06818E3-00A5-41AC-97ED-9491070CDEA1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E06818E3-00A5-41AC-97ED-9491070CDEA1}.Publish|Any CPU.ActiveCfg = Debug|Any CPU
@@ -861,6 +869,12 @@ Global
{4326A974-F027-4ABD-A220-382CC6BB0801}.Publish|Any CPU.Build.0 = Debug|Any CPU
{4326A974-F027-4ABD-A220-382CC6BB0801}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4326A974-F027-4ABD-A220-382CC6BB0801}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2A6B056D-B35A-4CCE-80EE-0307EA9C3A57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2A6B056D-B35A-4CCE-80EE-0307EA9C3A57}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2A6B056D-B35A-4CCE-80EE-0307EA9C3A57}.Publish|Any CPU.ActiveCfg = Debug|Any CPU
+ {2A6B056D-B35A-4CCE-80EE-0307EA9C3A57}.Publish|Any CPU.Build.0 = Debug|Any CPU
+ {2A6B056D-B35A-4CCE-80EE-0307EA9C3A57}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2A6B056D-B35A-4CCE-80EE-0307EA9C3A57}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -975,12 +989,14 @@ Global
{738DCDB1-EFA8-4913-AD4C-6FC3F09B0A0C} = {2E79AD99-632F-411F-B3A5-1BAF3F5F89AB}
{8642A03F-D840-4B2E-B092-478300000F83} = {0247C2C9-86C3-45BA-8873-28B0948EDC0C}
{ACD8C464-AEC9-45F6-A458-50A84F353DB7} = {0247C2C9-86C3-45BA-8873-28B0948EDC0C}
- {38374C62-0263-4FE8-A18C-70FC8132912B} = {5D4C0700-BBB5-418F-A7B2-F392B9A18263}
{E06818E3-00A5-41AC-97ED-9491070CDEA1} = {5D4C0700-BBB5-418F-A7B2-F392B9A18263}
{F8B82F6B-B16A-4F8C-9C41-E9CD8D79A098} = {5D4C0700-BBB5-418F-A7B2-F392B9A18263}
{6B268108-2AB5-4607-B246-06AD8410E60E} = {4BB70FB3-1EC0-4F4A-863B-273D6C6D4D9A}
{4BB70FB3-1EC0-4F4A-863B-273D6C6D4D9A} = {F8B82F6B-B16A-4F8C-9C41-E9CD8D79A098}
{4326A974-F027-4ABD-A220-382CC6BB0801} = {4BB70FB3-1EC0-4F4A-863B-273D6C6D4D9A}
+ {EE454832-085F-4D37-B19B-F94F7FC6984A} = {77E141BA-AF5E-4C01-A970-6C07AC3CD55A}
+ {5C6C30E0-7AC1-47F4-8244-57B066B43FD8} = {77E141BA-AF5E-4C01-A970-6C07AC3CD55A}
+ {2A6B056D-B35A-4CCE-80EE-0307EA9C3A57} = {5D4C0700-BBB5-418F-A7B2-F392B9A18263}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FBDC56A3-86AD-4323-AA0F-201E59123B83}
diff --git a/dotnet/samples/Concepts/Agents/ChatCompletion_FunctionTermination.cs b/dotnet/samples/Concepts/Agents/ChatCompletion_FunctionTermination.cs
index 16c019aebbfd..d0b8e92d39d7 100644
--- a/dotnet/samples/Concepts/Agents/ChatCompletion_FunctionTermination.cs
+++ b/dotnet/samples/Concepts/Agents/ChatCompletion_FunctionTermination.cs
@@ -12,7 +12,7 @@ namespace Agents;
/// Demonstrate usage of for both direction invocation
/// of and via .
///
-public class ChatCompletion_FunctionTermination(ITestOutputHelper output) : BaseTest(output)
+public class ChatCompletion_FunctionTermination(ITestOutputHelper output) : BaseAgentsTest(output)
{
[Fact]
public async Task UseAutoFunctionInvocationFilterWithAgentInvocationAsync()
@@ -44,25 +44,25 @@ public async Task UseAutoFunctionInvocationFilterWithAgentInvocationAsync()
Console.WriteLine("================================");
foreach (ChatMessageContent message in chat)
{
- this.WriteContent(message);
+ this.WriteAgentChatMessage(message);
}
// Local function to invoke agent and display the conversation messages.
async Task InvokeAgentAsync(string input)
{
- ChatMessageContent userContent = new(AuthorRole.User, input);
- chat.Add(userContent);
- this.WriteContent(userContent);
+ ChatMessageContent message = new(AuthorRole.User, input);
+ chat.Add(message);
+ this.WriteAgentChatMessage(message);
- await foreach (ChatMessageContent content in agent.InvokeAsync(chat))
+ await foreach (ChatMessageContent response in agent.InvokeAsync(chat))
{
// Do not add a message implicitly added to the history.
- if (!content.Items.Any(i => i is FunctionCallContent || i is FunctionResultContent))
+ if (!response.Items.Any(i => i is FunctionCallContent || i is FunctionResultContent))
{
- chat.Add(content);
+ chat.Add(response);
}
- this.WriteContent(content);
+ this.WriteAgentChatMessage(response);
}
}
}
@@ -98,28 +98,23 @@ public async Task UseAutoFunctionInvocationFilterWithAgentChatAsync()
ChatMessageContent[] history = await chat.GetChatMessagesAsync().ToArrayAsync();
for (int index = history.Length; index > 0; --index)
{
- this.WriteContent(history[index - 1]);
+ this.WriteAgentChatMessage(history[index - 1]);
}
// Local function to invoke agent and display the conversation messages.
async Task InvokeAgentAsync(string input)
{
- ChatMessageContent userContent = new(AuthorRole.User, input);
- chat.AddChatMessage(userContent);
- this.WriteContent(userContent);
+ ChatMessageContent message = new(AuthorRole.User, input);
+ chat.AddChatMessage(message);
+ this.WriteAgentChatMessage(message);
- await foreach (ChatMessageContent content in chat.InvokeAsync(agent))
+ await foreach (ChatMessageContent response in chat.InvokeAsync(agent))
{
- this.WriteContent(content);
+ this.WriteAgentChatMessage(response);
}
}
}
- private void WriteContent(ChatMessageContent content)
- {
- Console.WriteLine($"[{content.Items.LastOrDefault()?.GetType().Name ?? "(empty)"}] {content.Role} : '{content.Content}'");
- }
-
private Kernel CreateKernelWithFilter()
{
IKernelBuilder builder = Kernel.CreateBuilder();
diff --git a/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs b/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs
index d3e94386af96..575db7f7f288 100644
--- a/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs
+++ b/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs
@@ -12,7 +12,7 @@ namespace Agents;
/// Demonstrate creation of and
/// eliciting its response to three explicit user messages.
///
-public class ChatCompletion_Streaming(ITestOutputHelper output) : BaseTest(output)
+public class ChatCompletion_Streaming(ITestOutputHelper output) : BaseAgentsTest(output)
{
private const string ParrotName = "Parrot";
private const string ParrotInstructions = "Repeat the user message in the voice of a pirate and then end with a parrot sound.";
@@ -66,32 +66,33 @@ public async Task UseStreamingChatCompletionAgentWithPluginAsync()
// Local function to invoke agent and display the conversation messages.
private async Task InvokeAgentAsync(ChatCompletionAgent agent, ChatHistory chat, string input)
{
- chat.Add(new ChatMessageContent(AuthorRole.User, input));
-
- Console.WriteLine($"# {AuthorRole.User}: '{input}'");
+ ChatMessageContent message = new(AuthorRole.User, input);
+ chat.Add(message);
+ this.WriteAgentChatMessage(message);
StringBuilder builder = new();
- await foreach (StreamingChatMessageContent message in agent.InvokeStreamingAsync(chat))
+ await foreach (StreamingChatMessageContent response in agent.InvokeStreamingAsync(chat))
{
- if (string.IsNullOrEmpty(message.Content))
+ if (string.IsNullOrEmpty(response.Content))
{
continue;
}
if (builder.Length == 0)
{
- Console.WriteLine($"# {message.Role} - {message.AuthorName ?? "*"}:");
+ Console.WriteLine($"# {response.Role} - {response.AuthorName ?? "*"}:");
}
- Console.WriteLine($"\t > streamed: '{message.Content}'");
- builder.Append(message.Content);
+ Console.WriteLine($"\t > streamed: '{response.Content}'");
+ builder.Append(response.Content);
}
if (builder.Length > 0)
{
// Display full response and capture in chat history
- Console.WriteLine($"\t > complete: '{builder}'");
- chat.Add(new ChatMessageContent(AuthorRole.Assistant, builder.ToString()) { AuthorName = agent.Name });
+ ChatMessageContent response = new(AuthorRole.Assistant, builder.ToString()) { AuthorName = agent.Name };
+ chat.Add(response);
+ this.WriteAgentChatMessage(response);
}
}
diff --git a/dotnet/samples/Concepts/Agents/ComplexChat_NestedShopper.cs b/dotnet/samples/Concepts/Agents/ComplexChat_NestedShopper.cs
index 81b2914ade3b..0d7b27917d78 100644
--- a/dotnet/samples/Concepts/Agents/ComplexChat_NestedShopper.cs
+++ b/dotnet/samples/Concepts/Agents/ComplexChat_NestedShopper.cs
@@ -13,10 +13,8 @@ namespace Agents;
/// Demonstrate usage of and
/// to manage execution.
///
-public class ComplexChat_NestedShopper(ITestOutputHelper output) : BaseTest(output)
+public class ComplexChat_NestedShopper(ITestOutputHelper output) : BaseAgentsTest(output)
{
- protected override bool ForceOpenAI => true;
-
private const string InternalLeaderName = "InternalLeader";
private const string InternalLeaderInstructions =
"""
@@ -154,20 +152,20 @@ public async Task NestedChatWithAggregatorAgentAsync()
Console.WriteLine(">>>> AGGREGATED CHAT");
Console.WriteLine(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
- await foreach (ChatMessageContent content in chat.GetChatMessagesAsync(personalShopperAgent).Reverse())
+ await foreach (ChatMessageContent message in chat.GetChatMessagesAsync(personalShopperAgent).Reverse())
{
- Console.WriteLine($">>>> {content.Role} - {content.AuthorName ?? "*"}: '{content.Content}'");
+ this.WriteAgentChatMessage(message);
}
async Task InvokeChatAsync(string input)
{
- chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input));
-
- Console.WriteLine($"# {AuthorRole.User}: '{input}'");
+ ChatMessageContent message = new(AuthorRole.User, input);
+ chat.AddChatMessage(message);
+ this.WriteAgentChatMessage(message);
- await foreach (ChatMessageContent content in chat.InvokeAsync(personalShopperAgent))
+ await foreach (ChatMessageContent response in chat.InvokeAsync(personalShopperAgent))
{
- Console.WriteLine($"# {content.Role} - {content.AuthorName ?? "*"}: '{content.Content}'");
+ this.WriteAgentChatMessage(response);
}
Console.WriteLine($"\n# IS COMPLETE: {chat.IsComplete}");
diff --git a/dotnet/samples/Concepts/Agents/Legacy_AgentAuthoring.cs b/dotnet/samples/Concepts/Agents/Legacy_AgentAuthoring.cs
index 062262fe8a8c..53276c75a24d 100644
--- a/dotnet/samples/Concepts/Agents/Legacy_AgentAuthoring.cs
+++ b/dotnet/samples/Concepts/Agents/Legacy_AgentAuthoring.cs
@@ -9,12 +9,6 @@ namespace Agents;
///
public class Legacy_AgentAuthoring(ITestOutputHelper output) : BaseTest(output)
{
- ///
- /// Specific model is required that supports agents and parallel function calling.
- /// Currently this is limited to Open AI hosted services.
- ///
- private const string OpenAIFunctionEnabledModel = "gpt-4-1106-preview";
-
// Track agents for clean-up
private static readonly List s_agents = [];
@@ -72,7 +66,7 @@ private static async Task CreateArticleGeneratorAsync()
return
Track(
await new AgentBuilder()
- .WithOpenAIChatCompletion(OpenAIFunctionEnabledModel, TestConfiguration.OpenAI.ApiKey)
+ .WithOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey)
.WithInstructions("You write concise opinionated articles that are published online. Use an outline to generate an article with one section of prose for each top-level outline element. Each section is based on research with a maximum of 120 words.")
.WithName("Article Author")
.WithDescription("Author an article on a given topic.")
@@ -87,7 +81,7 @@ private static async Task CreateOutlineGeneratorAsync()
return
Track(
await new AgentBuilder()
- .WithOpenAIChatCompletion(OpenAIFunctionEnabledModel, TestConfiguration.OpenAI.ApiKey)
+ .WithOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey)
.WithInstructions("Produce an single-level outline (no child elements) based on the given topic with at most 3 sections.")
.WithName("Outline Generator")
.WithDescription("Generate an outline.")
@@ -100,7 +94,7 @@ private static async Task CreateResearchGeneratorAsync()
return
Track(
await new AgentBuilder()
- .WithOpenAIChatCompletion(OpenAIFunctionEnabledModel, TestConfiguration.OpenAI.ApiKey)
+ .WithOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey)
.WithInstructions("Provide insightful research that supports the given topic based on your knowledge of the outline topic.")
.WithName("Researcher")
.WithDescription("Author research summary.")
diff --git a/dotnet/samples/Concepts/Agents/Legacy_AgentCharts.cs b/dotnet/samples/Concepts/Agents/Legacy_AgentCharts.cs
index b64f183adbc8..d40755101309 100644
--- a/dotnet/samples/Concepts/Agents/Legacy_AgentCharts.cs
+++ b/dotnet/samples/Concepts/Agents/Legacy_AgentCharts.cs
@@ -1,8 +1,10 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Diagnostics;
-using Microsoft.SemanticKernel.Connectors.OpenAI;
+using Azure.AI.OpenAI;
using Microsoft.SemanticKernel.Experimental.Agents;
+using OpenAI;
+using OpenAI.Files;
namespace Agents;
@@ -12,28 +14,15 @@ namespace Agents;
///
public sealed class Legacy_AgentCharts(ITestOutputHelper output) : BaseTest(output)
{
- ///
- /// Specific model is required that supports agents and parallel function calling.
- /// Currently this is limited to Open AI hosted services.
- ///
- private const string OpenAIFunctionEnabledModel = "gpt-4-1106-preview";
-
- ///
- /// Flag to force usage of OpenAI configuration if both
- /// and are defined.
- /// If 'false', Azure takes precedence.
- ///
- private new const bool ForceOpenAI = false;
-
///
/// Create a chart and retrieve by file_id.
///
- [Fact(Skip = "Launches external processes")]
+ [Fact]
public async Task CreateChartAsync()
{
Console.WriteLine("======== Using CodeInterpreter tool ========");
- var fileService = CreateFileService();
+ FileClient fileClient = CreateFileClient();
var agent = await CreateAgentBuilder().WithCodeInterpreter().BuildAsync();
@@ -69,11 +58,11 @@ async Task InvokeAgentAsync(IAgentThread thread, string imageName, string questi
{
var filename = $"{imageName}.jpg";
var path = Path.Combine(Environment.CurrentDirectory, filename);
- Console.WriteLine($"# {message.Role}: {message.Content}");
+ var fileId = message.Content;
+ Console.WriteLine($"# {message.Role}: {fileId}");
Console.WriteLine($"# {message.Role}: {path}");
- var content = await fileService.GetFileContentAsync(message.Content);
- await using var outputStream = File.OpenWrite(filename);
- await outputStream.WriteAsync(content.Data!.Value);
+ BinaryData content = await fileClient.DownloadFileAsync(fileId);
+ File.WriteAllBytes(filename, content.ToArray());
Process.Start(
new ProcessStartInfo
{
@@ -91,22 +80,23 @@ async Task InvokeAgentAsync(IAgentThread thread, string imageName, string questi
}
}
-#pragma warning disable CS0618 // Type or member is obsolete
- private static OpenAIFileService CreateFileService()
+ private FileClient CreateFileClient()
{
- return
- ForceOpenAI || string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.Endpoint) ?
- new OpenAIFileService(TestConfiguration.OpenAI.ApiKey) :
- new OpenAIFileService(new Uri(TestConfiguration.AzureOpenAI.Endpoint), apiKey: TestConfiguration.AzureOpenAI.ApiKey);
+ OpenAIClient client =
+ this.ForceOpenAI || string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.Endpoint) ?
+ new OpenAIClient(TestConfiguration.OpenAI.ApiKey) :
+ new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAI.Endpoint), TestConfiguration.AzureOpenAI.ApiKey);
+
+ return client.GetFileClient();
}
#pragma warning restore CS0618 // Type or member is obsolete
- private static AgentBuilder CreateAgentBuilder()
+ private AgentBuilder CreateAgentBuilder()
{
return
- ForceOpenAI || string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.Endpoint) ?
- new AgentBuilder().WithOpenAIChatCompletion(OpenAIFunctionEnabledModel, TestConfiguration.OpenAI.ApiKey) :
+ this.ForceOpenAI || string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.Endpoint) ?
+ new AgentBuilder().WithOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey) :
new AgentBuilder().WithAzureOpenAIChatCompletion(TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ChatDeploymentName, TestConfiguration.AzureOpenAI.ApiKey);
}
}
diff --git a/dotnet/samples/Concepts/Agents/Legacy_AgentCollaboration.cs b/dotnet/samples/Concepts/Agents/Legacy_AgentCollaboration.cs
index 53ae0c07662a..fa257d2764b3 100644
--- a/dotnet/samples/Concepts/Agents/Legacy_AgentCollaboration.cs
+++ b/dotnet/samples/Concepts/Agents/Legacy_AgentCollaboration.cs
@@ -9,17 +9,6 @@ namespace Agents;
///
public class Legacy_AgentCollaboration(ITestOutputHelper output) : BaseTest(output)
{
- ///
- /// Specific model is required that supports agents and function calling.
- /// Currently this is limited to Open AI hosted services.
- ///
- private const string OpenAIFunctionEnabledModel = "gpt-4-turbo-preview";
-
- ///
- /// Set this to 'true' to target OpenAI instead of Azure OpenAI.
- ///
- private const bool UseOpenAI = false;
-
// Track agents for clean-up
private static readonly List s_agents = [];
@@ -29,8 +18,6 @@ public class Legacy_AgentCollaboration(ITestOutputHelper output) : BaseTest(outp
[Fact(Skip = "This test take more than 5 minutes to execute")]
public async Task RunCollaborationAsync()
{
- Console.WriteLine($"======== Example72:Collaboration:{(UseOpenAI ? "OpenAI" : "AzureAI")} ========");
-
IAgentThread? thread = null;
try
{
@@ -82,8 +69,6 @@ public async Task RunCollaborationAsync()
[Fact(Skip = "This test take more than 2 minutes to execute")]
public async Task RunAsPluginsAsync()
{
- Console.WriteLine($"======== Example72:AsPlugins:{(UseOpenAI ? "OpenAI" : "AzureAI")} ========");
-
try
{
// Create copy-writer agent to generate ideas
@@ -113,7 +98,7 @@ await CreateAgentBuilder()
}
}
- private static async Task CreateCopyWriterAsync(IAgent? agent = null)
+ private async Task CreateCopyWriterAsync(IAgent? agent = null)
{
return
Track(
@@ -125,7 +110,7 @@ await CreateAgentBuilder()
.BuildAsync());
}
- private static async Task CreateArtDirectorAsync()
+ private async Task CreateArtDirectorAsync()
{
return
Track(
@@ -136,13 +121,13 @@ await CreateAgentBuilder()
.BuildAsync());
}
- private static AgentBuilder CreateAgentBuilder()
+ private AgentBuilder CreateAgentBuilder()
{
var builder = new AgentBuilder();
return
- UseOpenAI ?
- builder.WithOpenAIChatCompletion(OpenAIFunctionEnabledModel, TestConfiguration.OpenAI.ApiKey) :
+ this.ForceOpenAI || string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.Endpoint) ?
+ builder.WithOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey) :
builder.WithAzureOpenAIChatCompletion(TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ChatDeploymentName, TestConfiguration.AzureOpenAI.ApiKey);
}
diff --git a/dotnet/samples/Concepts/Agents/Legacy_AgentDelegation.cs b/dotnet/samples/Concepts/Agents/Legacy_AgentDelegation.cs
index 86dacb9c256d..b4b0ed93199f 100644
--- a/dotnet/samples/Concepts/Agents/Legacy_AgentDelegation.cs
+++ b/dotnet/samples/Concepts/Agents/Legacy_AgentDelegation.cs
@@ -12,12 +12,6 @@ namespace Agents;
///
public class Legacy_AgentDelegation(ITestOutputHelper output) : BaseTest(output)
{
- ///
- /// Specific model is required that supports agents and function calling.
- /// Currently this is limited to Open AI hosted services.
- ///
- private const string OpenAIFunctionEnabledModel = "gpt-3.5-turbo-1106";
-
// Track agents for clean-up
private static readonly List s_agents = [];
@@ -27,8 +21,6 @@ public class Legacy_AgentDelegation(ITestOutputHelper output) : BaseTest(output)
[Fact]
public async Task RunAsync()
{
- Console.WriteLine("======== Example71_AgentDelegation ========");
-
if (TestConfiguration.OpenAI.ApiKey is null)
{
Console.WriteLine("OpenAI apiKey not found. Skipping example.");
@@ -39,11 +31,11 @@ public async Task RunAsync()
try
{
- var plugin = KernelPluginFactory.CreateFromType();
+ var plugin = KernelPluginFactory.CreateFromType();
var menuAgent =
Track(
await new AgentBuilder()
- .WithOpenAIChatCompletion(OpenAIFunctionEnabledModel, TestConfiguration.OpenAI.ApiKey)
+ .WithOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey)
.FromTemplate(EmbeddedResource.Read("Agents.ToolAgent.yaml"))
.WithDescription("Answer questions about how the menu uses the tool.")
.WithPlugin(plugin)
@@ -52,14 +44,14 @@ public async Task RunAsync()
var parrotAgent =
Track(
await new AgentBuilder()
- .WithOpenAIChatCompletion(OpenAIFunctionEnabledModel, TestConfiguration.OpenAI.ApiKey)
+ .WithOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey)
.FromTemplate(EmbeddedResource.Read("Agents.ParrotAgent.yaml"))
.BuildAsync());
var toolAgent =
Track(
await new AgentBuilder()
- .WithOpenAIChatCompletion(OpenAIFunctionEnabledModel, TestConfiguration.OpenAI.ApiKey)
+ .WithOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey)
.FromTemplate(EmbeddedResource.Read("Agents.ToolAgent.yaml"))
.WithPlugin(parrotAgent.AsPlugin())
.WithPlugin(menuAgent.AsPlugin())
diff --git a/dotnet/samples/Concepts/Agents/Legacy_AgentTools.cs b/dotnet/samples/Concepts/Agents/Legacy_AgentTools.cs
index c75a5e403cea..00af8faab617 100644
--- a/dotnet/samples/Concepts/Agents/Legacy_AgentTools.cs
+++ b/dotnet/samples/Concepts/Agents/Legacy_AgentTools.cs
@@ -1,8 +1,9 @@
// Copyright (c) Microsoft. All rights reserved.
-using Microsoft.SemanticKernel;
-using Microsoft.SemanticKernel.Connectors.OpenAI;
+using Azure.AI.OpenAI;
using Microsoft.SemanticKernel.Experimental.Agents;
+using OpenAI;
+using OpenAI.Files;
using Resources;
namespace Agents;
@@ -13,21 +14,8 @@ namespace Agents;
///
public sealed class Legacy_AgentTools(ITestOutputHelper output) : BaseTest(output)
{
- ///
- /// Specific model is required that supports agents and parallel function calling.
- /// Currently this is limited to Open AI hosted services.
- ///
- private const string OpenAIFunctionEnabledModel = "gpt-4-1106-preview";
-
- ///
- /// Flag to force usage of OpenAI configuration if both
- /// and are defined.
- /// If 'false', Azure takes precedence.
- ///
- ///
- /// NOTE: Retrieval tools is not currently available on Azure.
- ///
- private new const bool ForceOpenAI = true;
+ ///
+ protected override bool ForceOpenAI => true;
// Track agents for clean-up
private readonly List _agents = [];
@@ -79,14 +67,13 @@ public async Task RunRetrievalToolAsync()
return;
}
- Kernel kernel = CreateFileEnabledKernel();
-#pragma warning disable CS0618 // Type or member is obsolete
- var fileService = kernel.GetRequiredService();
- var result =
- await fileService.UploadContentAsync(
- new BinaryContent(await EmbeddedResource.ReadAllAsync("travelinfo.txt")!, "text/plain"),
- new OpenAIFileUploadExecutionSettings("travelinfo.txt", OpenAIFilePurpose.Assistants));
-#pragma warning restore CS0618 // Type or member is obsolete
+ FileClient fileClient = CreateFileClient();
+
+ OpenAIFileInfo result =
+ await fileClient.UploadFileAsync(
+ new BinaryData(await EmbeddedResource.ReadAllAsync("travelinfo.txt")!),
+ "travelinfo.txt",
+ FileUploadPurpose.Assistants);
var fileId = result.Id;
Console.WriteLine($"! {fileId}");
@@ -112,7 +99,7 @@ await ChatAsync(
}
finally
{
- await Task.WhenAll(this._agents.Select(a => a.DeleteAsync()).Append(fileService.DeleteFileAsync(fileId)));
+ await Task.WhenAll(this._agents.Select(a => a.DeleteAsync()).Append(fileClient.DeleteFileAsync(fileId)));
}
}
@@ -167,21 +154,21 @@ async Task InvokeAgentAsync(IAgent agent, string question)
}
}
- private static Kernel CreateFileEnabledKernel()
+ private FileClient CreateFileClient()
{
-#pragma warning disable CS0618 // Type or member is obsolete
- return
- ForceOpenAI || string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.Endpoint) ?
- Kernel.CreateBuilder().AddOpenAIFiles(TestConfiguration.OpenAI.ApiKey).Build() :
- throw new NotImplementedException("The file service is being deprecated and was not moved to AzureOpenAI connector.");
-#pragma warning restore CS0618 // Type or member is obsolete
+ OpenAIClient client =
+ this.ForceOpenAI || string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.Endpoint) ?
+ new OpenAIClient(TestConfiguration.OpenAI.ApiKey) :
+ new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAI.Endpoint), TestConfiguration.AzureOpenAI.ApiKey);
+
+ return client.GetFileClient();
}
- private static AgentBuilder CreateAgentBuilder()
+ private AgentBuilder CreateAgentBuilder()
{
return
- ForceOpenAI || string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.Endpoint) ?
- new AgentBuilder().WithOpenAIChatCompletion(OpenAIFunctionEnabledModel, TestConfiguration.OpenAI.ApiKey) :
+ this.ForceOpenAI || string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.Endpoint) ?
+ new AgentBuilder().WithOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey) :
new AgentBuilder().WithAzureOpenAIChatCompletion(TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ChatDeploymentName, TestConfiguration.AzureOpenAI.ApiKey);
}
diff --git a/dotnet/samples/Concepts/Agents/Legacy_Agents.cs b/dotnet/samples/Concepts/Agents/Legacy_Agents.cs
index 5af10987bb3a..31cc4926392b 100644
--- a/dotnet/samples/Concepts/Agents/Legacy_Agents.cs
+++ b/dotnet/samples/Concepts/Agents/Legacy_Agents.cs
@@ -13,19 +13,6 @@ namespace Agents;
///
public class Legacy_Agents(ITestOutputHelper output) : BaseTest(output)
{
- ///
- /// Specific model is required that supports agents and function calling.
- /// Currently this is limited to Open AI hosted services.
- ///
- private const string OpenAIFunctionEnabledModel = "gpt-3.5-turbo-1106";
-
- ///
- /// Flag to force usage of OpenAI configuration if both
- /// and are defined.
- /// If 'false', Azure takes precedence.
- ///
- private new const bool ForceOpenAI = false;
-
///
/// Chat using the "Parrot" agent.
/// Tools/functions: None
@@ -61,18 +48,12 @@ public async Task RunWithMethodFunctionsAsync()
await ChatAsync(
"Agents.ToolAgent.yaml", // Defined under ./Resources/Agents
plugin,
- arguments: new() { { LegacyMenuPlugin.CorrelationIdArgument, 3.141592653 } },
+ arguments: null,
"Hello",
"What is the special soup?",
"What is the special drink?",
"Do you have enough soup for 5 orders?",
"Thank you!");
-
- Console.WriteLine("\nCorrelation Ids:");
- foreach (string correlationId in menuApi.CorrelationIds)
- {
- Console.WriteLine($"- {correlationId}");
- }
}
///
@@ -114,7 +95,7 @@ public async Task RunAsFunctionAsync()
// Create parrot agent, same as the other cases.
var agent =
await new AgentBuilder()
- .WithOpenAIChatCompletion(OpenAIFunctionEnabledModel, TestConfiguration.OpenAI.ApiKey)
+ .WithOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey)
.FromTemplate(EmbeddedResource.Read("Agents.ParrotAgent.yaml"))
.BuildAsync();
@@ -187,11 +168,11 @@ await Task.WhenAll(
}
}
- private static AgentBuilder CreateAgentBuilder()
+ private AgentBuilder CreateAgentBuilder()
{
return
- ForceOpenAI || string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.Endpoint) ?
- new AgentBuilder().WithOpenAIChatCompletion(OpenAIFunctionEnabledModel, TestConfiguration.OpenAI.ApiKey) :
+ this.ForceOpenAI || string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.Endpoint) ?
+ new AgentBuilder().WithOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey) :
new AgentBuilder().WithAzureOpenAIChatCompletion(TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ChatDeploymentName, TestConfiguration.AzureOpenAI.ApiKey);
}
}
diff --git a/dotnet/samples/Concepts/Agents/MixedChat_Agents.cs b/dotnet/samples/Concepts/Agents/MixedChat_Agents.cs
index d3a894dd6c8e..21b19c1d342c 100644
--- a/dotnet/samples/Concepts/Agents/MixedChat_Agents.cs
+++ b/dotnet/samples/Concepts/Agents/MixedChat_Agents.cs
@@ -10,7 +10,7 @@ namespace Agents;
/// Demonstrate that two different agent types are able to participate in the same conversation.
/// In this case a and participate.
///
-public class MixedChat_Agents(ITestOutputHelper output) : BaseTest(output)
+public class MixedChat_Agents(ITestOutputHelper output) : BaseAgentsTest(output)
{
private const string ReviewerName = "ArtDirector";
private const string ReviewerInstructions =
@@ -47,12 +47,12 @@ public async Task ChatWithOpenAIAssistantAgentAndChatCompletionAgentAsync()
OpenAIAssistantAgent agentWriter =
await OpenAIAssistantAgent.CreateAsync(
kernel: new(),
- config: new(this.ApiKey, this.Endpoint),
- definition: new()
+ clientProvider: this.GetClientProvider(),
+ definition: new(this.Model)
{
Instructions = CopyWriterInstructions,
Name = CopyWriterName,
- ModelId = this.Model,
+ Metadata = AssistantSampleMetadata,
});
// Create a chat for agent interaction.
@@ -76,16 +76,16 @@ await OpenAIAssistantAgent.CreateAsync(
};
// Invoke chat and display messages.
- string input = "concept: maps made out of egg cartons.";
- chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input));
- Console.WriteLine($"# {AuthorRole.User}: '{input}'");
+ ChatMessageContent input = new(AuthorRole.User, "concept: maps made out of egg cartons.");
+ chat.AddChatMessage(input);
+ this.WriteAgentChatMessage(input);
- await foreach (ChatMessageContent content in chat.InvokeAsync())
+ await foreach (ChatMessageContent response in chat.InvokeAsync())
{
- Console.WriteLine($"# {content.Role} - {content.AuthorName ?? "*"}: '{content.Content}'");
+ this.WriteAgentChatMessage(response);
}
- Console.WriteLine($"# IS COMPLETE: {chat.IsComplete}");
+ Console.WriteLine($"\n[IS COMPLETED: {chat.IsComplete}]");
}
private sealed class ApprovalTerminationStrategy : TerminationStrategy
diff --git a/dotnet/samples/Concepts/Agents/MixedChat_Files.cs b/dotnet/samples/Concepts/Agents/MixedChat_Files.cs
index b95c6efca36d..0219c25f7712 100644
--- a/dotnet/samples/Concepts/Agents/MixedChat_Files.cs
+++ b/dotnet/samples/Concepts/Agents/MixedChat_Files.cs
@@ -1,10 +1,9 @@
// Copyright (c) Microsoft. All rights reserved.
-using System.Text;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.OpenAI;
using Microsoft.SemanticKernel.ChatCompletion;
-using Microsoft.SemanticKernel.Connectors.OpenAI;
+using OpenAI.Files;
using Resources;
namespace Agents;
@@ -13,25 +12,22 @@ namespace Agents;
/// Demonstrate agent interacts with
/// when it produces file output.
///
-public class MixedChat_Files(ITestOutputHelper output) : BaseTest(output)
+public class MixedChat_Files(ITestOutputHelper output) : BaseAgentsTest(output)
{
- ///
- /// Target OpenAI services.
- ///
- protected override bool ForceOpenAI => true;
-
private const string SummaryInstructions = "Summarize the entire conversation for the user in natural language.";
[Fact]
public async Task AnalyzeFileAndGenerateReportAsync()
{
-#pragma warning disable CS0618 // Type or member is obsolete
- OpenAIFileService fileService = new(TestConfiguration.OpenAI.ApiKey);
+ OpenAIClientProvider provider = this.GetClientProvider();
+
+ FileClient fileClient = provider.Client.GetFileClient();
- OpenAIFileReference uploadFile =
- await fileService.UploadContentAsync(
- new BinaryContent(await EmbeddedResource.ReadAllAsync("30-user-context.txt"), mimeType: "text/plain"),
- new OpenAIFileUploadExecutionSettings("30-user-context.txt", OpenAIFilePurpose.Assistants));
+ OpenAIFileInfo uploadFile =
+ await fileClient.UploadFileAsync(
+ new BinaryData(await EmbeddedResource.ReadAllAsync("30-user-context.txt")),
+ "30-user-context.txt",
+ FileUploadPurpose.Assistants);
Console.WriteLine(this.ApiKey);
@@ -39,12 +35,12 @@ await fileService.UploadContentAsync(
OpenAIAssistantAgent analystAgent =
await OpenAIAssistantAgent.CreateAsync(
kernel: new(),
- config: new(this.ApiKey, this.Endpoint),
- new()
+ provider,
+ new(this.Model)
{
- EnableCodeInterpreter = true, // Enable code-interpreter
- ModelId = this.Model,
- FileIds = [uploadFile.Id] // Associate uploaded file with assistant
+ EnableCodeInterpreter = true,
+ CodeInterpreterFileIds = [uploadFile.Id], // Associate uploaded file with assistant code-interpreter
+ Metadata = AssistantSampleMetadata,
});
ChatCompletionAgent summaryAgent =
@@ -71,7 +67,7 @@ Create a tab delimited file report of the ordered (descending) frequency distrib
finally
{
await analystAgent.DeleteAsync();
- await fileService.DeleteFileAsync(uploadFile.Id);
+ await fileClient.DeleteFileAsync(uploadFile.Id);
}
// Local function to invoke agent and display the conversation messages.
@@ -79,23 +75,16 @@ async Task InvokeAgentAsync(Agent agent, string? input = null)
{
if (!string.IsNullOrWhiteSpace(input))
{
+ ChatMessageContent message = new(AuthorRole.User, input);
chat.AddChatMessage(new(AuthorRole.User, input));
- Console.WriteLine($"# {AuthorRole.User}: '{input}'");
+ this.WriteAgentChatMessage(message);
}
- await foreach (ChatMessageContent content in chat.InvokeAsync(agent))
+ await foreach (ChatMessageContent response in chat.InvokeAsync(agent))
{
- Console.WriteLine($"\n# {content.Role} - {content.AuthorName ?? "*"}: '{content.Content}'");
-
- foreach (AnnotationContent annotation in content.Items.OfType())
- {
- Console.WriteLine($"\t* '{annotation.Quote}' => {annotation.FileId}");
- BinaryContent fileContent = await fileService.GetFileContentAsync(annotation.FileId!);
- byte[] byteContent = fileContent.Data?.ToArray() ?? [];
- Console.WriteLine($"\n{Encoding.Default.GetString(byteContent)}");
- }
+ this.WriteAgentChatMessage(response);
+ await this.DownloadResponseContentAsync(fileClient, response);
}
}
-#pragma warning restore CS0618 // Type or member is obsolete
}
}
diff --git a/dotnet/samples/Concepts/Agents/MixedChat_Images.cs b/dotnet/samples/Concepts/Agents/MixedChat_Images.cs
index 36b96fc4be54..142706e8506c 100644
--- a/dotnet/samples/Concepts/Agents/MixedChat_Images.cs
+++ b/dotnet/samples/Concepts/Agents/MixedChat_Images.cs
@@ -3,7 +3,7 @@
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.OpenAI;
using Microsoft.SemanticKernel.ChatCompletion;
-using Microsoft.SemanticKernel.Connectors.OpenAI;
+using OpenAI.Files;
namespace Agents;
@@ -11,13 +11,8 @@ namespace Agents;
/// Demonstrate agent interacts with
/// when it produces image output.
///
-public class MixedChat_Images(ITestOutputHelper output) : BaseTest(output)
+public class MixedChat_Images(ITestOutputHelper output) : BaseAgentsTest(output)
{
- ///
- /// Target OpenAI services.
- ///
- protected override bool ForceOpenAI => true;
-
private const string AnalystName = "Analyst";
private const string AnalystInstructions = "Create charts as requested without explanation.";
@@ -27,20 +22,21 @@ public class MixedChat_Images(ITestOutputHelper output) : BaseTest(output)
[Fact]
public async Task AnalyzeDataAndGenerateChartAsync()
{
-#pragma warning disable CS0618 // Type or member is obsolete
- OpenAIFileService fileService = new(TestConfiguration.OpenAI.ApiKey);
+ OpenAIClientProvider provider = this.GetClientProvider();
+
+ FileClient fileClient = provider.Client.GetFileClient();
// Define the agents
OpenAIAssistantAgent analystAgent =
await OpenAIAssistantAgent.CreateAsync(
kernel: new(),
- config: new(this.ApiKey, this.Endpoint),
- new()
+ provider,
+ new(this.Model)
{
Instructions = AnalystInstructions,
Name = AnalystName,
EnableCodeInterpreter = true,
- ModelId = this.Model,
+ Metadata = AssistantSampleMetadata,
});
ChatCompletionAgent summaryAgent =
@@ -87,28 +83,16 @@ async Task InvokeAgentAsync(Agent agent, string? input = null)
{
if (!string.IsNullOrWhiteSpace(input))
{
+ ChatMessageContent message = new(AuthorRole.User, input);
chat.AddChatMessage(new(AuthorRole.User, input));
- Console.WriteLine($"# {AuthorRole.User}: '{input}'");
+ this.WriteAgentChatMessage(message);
}
- await foreach (ChatMessageContent message in chat.InvokeAsync(agent))
+ await foreach (ChatMessageContent response in chat.InvokeAsync(agent))
{
- if (!string.IsNullOrWhiteSpace(message.Content))
- {
- Console.WriteLine($"\n# {message.Role} - {message.AuthorName ?? "*"}: '{message.Content}'");
- }
-
- foreach (FileReferenceContent fileReference in message.Items.OfType())
- {
- Console.WriteLine($"\t* Generated image - @{fileReference.FileId}");
- BinaryContent fileContent = await fileService.GetFileContentAsync(fileReference.FileId!);
- byte[] byteContent = fileContent.Data?.ToArray() ?? [];
- string filePath = Path.ChangeExtension(Path.GetTempFileName(), ".png");
- await File.WriteAllBytesAsync($"{filePath}.png", byteContent);
- Console.WriteLine($"\t* Local path - {filePath}");
- }
+ this.WriteAgentChatMessage(response);
+ await this.DownloadResponseImageAsync(fileClient, response);
}
}
-#pragma warning restore CS0618 // Type or member is obsolete
}
}
diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs
index ef5ba80154fa..cd81f7c4d187 100644
--- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs
+++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_ChartMaker.cs
@@ -3,6 +3,7 @@
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.OpenAI;
using Microsoft.SemanticKernel.ChatCompletion;
+using OpenAI.Files;
namespace Agents;
@@ -10,30 +11,29 @@ namespace Agents;
/// Demonstrate using code-interpreter with to
/// produce image content displays the requested charts.
///
-public class OpenAIAssistant_ChartMaker(ITestOutputHelper output) : BaseTest(output)
+public class OpenAIAssistant_ChartMaker(ITestOutputHelper output) : BaseAgentsTest(output)
{
- ///
- /// Target Open AI services.
- ///
- protected override bool ForceOpenAI => true;
-
private const string AgentName = "ChartMaker";
private const string AgentInstructions = "Create charts as requested without explanation.";
[Fact]
public async Task GenerateChartWithOpenAIAssistantAgentAsync()
{
+ OpenAIClientProvider provider = this.GetClientProvider();
+
+ FileClient fileClient = provider.Client.GetFileClient();
+
// Define the agent
OpenAIAssistantAgent agent =
await OpenAIAssistantAgent.CreateAsync(
kernel: new(),
- config: new(this.ApiKey, this.Endpoint),
- new()
+ provider,
+ new(this.Model)
{
Instructions = AgentInstructions,
Name = AgentName,
EnableCodeInterpreter = true,
- ModelId = this.Model,
+ Metadata = AssistantSampleMetadata,
});
// Create a chat for agent interaction.
@@ -55,6 +55,7 @@ Sum 426 1622 856 2904
""");
await InvokeAgentAsync("Can you regenerate this same chart using the category names as the bar colors?");
+ await InvokeAgentAsync("Perfect, can you regenerate this as a line chart?");
}
finally
{
@@ -64,21 +65,14 @@ Sum 426 1622 856 2904
// Local function to invoke agent and display the conversation messages.
async Task InvokeAgentAsync(string input)
{
- chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input));
-
- Console.WriteLine($"# {AuthorRole.User}: '{input}'");
+ ChatMessageContent message = new(AuthorRole.User, input);
+ chat.AddChatMessage(new(AuthorRole.User, input));
+ this.WriteAgentChatMessage(message);
- await foreach (ChatMessageContent message in chat.InvokeAsync(agent))
+ await foreach (ChatMessageContent response in chat.InvokeAsync(agent))
{
- if (!string.IsNullOrWhiteSpace(message.Content))
- {
- Console.WriteLine($"# {message.Role} - {message.AuthorName ?? "*"}: '{message.Content}'");
- }
-
- foreach (FileReferenceContent fileReference in message.Items.OfType())
- {
- Console.WriteLine($"# {message.Role} - {message.AuthorName ?? "*"}: @{fileReference.FileId}");
- }
+ this.WriteAgentChatMessage(response);
+ await this.DownloadResponseImageAsync(fileClient, response);
}
}
}
diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs
index f99130790eef..dc4af2ad2743 100644
--- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs
+++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileManipulation.cs
@@ -1,10 +1,9 @@
// Copyright (c) Microsoft. All rights reserved.
-using System.Text;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.OpenAI;
using Microsoft.SemanticKernel.ChatCompletion;
-using Microsoft.SemanticKernel.Connectors.OpenAI;
+using OpenAI.Files;
using Resources;
namespace Agents;
@@ -12,38 +11,31 @@ namespace Agents;
///
/// Demonstrate using code-interpreter to manipulate and generate csv files with .
///
-public class OpenAIAssistant_FileManipulation(ITestOutputHelper output) : BaseTest(output)
+public class OpenAIAssistant_FileManipulation(ITestOutputHelper output) : BaseAgentsTest(output)
{
- ///
- /// Target OpenAI services.
- ///
- protected override bool ForceOpenAI => true;
-
[Fact]
public async Task AnalyzeCSVFileUsingOpenAIAssistantAgentAsync()
{
-#pragma warning disable CS0618 // Type or member is obsolete
- OpenAIFileService fileService = new(TestConfiguration.OpenAI.ApiKey);
-
- OpenAIFileReference uploadFile =
- await fileService.UploadContentAsync(
- new BinaryContent(await EmbeddedResource.ReadAllAsync("sales.csv"), mimeType: "text/plain"),
- new OpenAIFileUploadExecutionSettings("sales.csv", OpenAIFilePurpose.Assistants));
+ OpenAIClientProvider provider = this.GetClientProvider();
-#pragma warning restore CS0618 // Type or member is obsolete
+ FileClient fileClient = provider.Client.GetFileClient();
- Console.WriteLine(this.ApiKey);
+ OpenAIFileInfo uploadFile =
+ await fileClient.UploadFileAsync(
+ new BinaryData(await EmbeddedResource.ReadAllAsync("sales.csv")!),
+ "sales.csv",
+ FileUploadPurpose.Assistants);
// Define the agent
OpenAIAssistantAgent agent =
await OpenAIAssistantAgent.CreateAsync(
kernel: new(),
- config: new(this.ApiKey, this.Endpoint),
- new()
+ provider,
+ new(this.Model)
{
- EnableCodeInterpreter = true, // Enable code-interpreter
- ModelId = this.Model,
- FileIds = [uploadFile.Id] // Associate uploaded file
+ EnableCodeInterpreter = true,
+ CodeInterpreterFileIds = [uploadFile.Id],
+ Metadata = AssistantSampleMetadata,
});
// Create a chat for agent interaction.
@@ -59,27 +51,20 @@ await OpenAIAssistantAgent.CreateAsync(
finally
{
await agent.DeleteAsync();
- await fileService.DeleteFileAsync(uploadFile.Id);
+ await fileClient.DeleteFileAsync(uploadFile.Id);
}
// Local function to invoke agent and display the conversation messages.
async Task InvokeAgentAsync(string input)
{
- chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input));
-
- Console.WriteLine($"# {AuthorRole.User}: '{input}'");
+ ChatMessageContent message = new(AuthorRole.User, input);
+ chat.AddChatMessage(new(AuthorRole.User, input));
+ this.WriteAgentChatMessage(message);
- await foreach (ChatMessageContent content in chat.InvokeAsync(agent))
+ await foreach (ChatMessageContent response in chat.InvokeAsync(agent))
{
- Console.WriteLine($"# {content.Role} - {content.AuthorName ?? "*"}: '{content.Content}'");
-
- foreach (AnnotationContent annotation in content.Items.OfType())
- {
- Console.WriteLine($"\n* '{annotation.Quote}' => {annotation.FileId}");
- BinaryContent fileContent = await fileService.GetFileContentAsync(annotation.FileId!);
- byte[] byteContent = fileContent.Data?.ToArray() ?? [];
- Console.WriteLine(Encoding.Default.GetString(byteContent));
- }
+ this.WriteAgentChatMessage(response);
+ await this.DownloadResponseContentAsync(fileClient, response);
}
}
}
diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileService.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileService.cs
index 38bac46f648a..a8f31622c753 100644
--- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileService.cs
+++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_FileService.cs
@@ -28,7 +28,7 @@ public async Task UploadAndRetrieveFilesAsync()
new BinaryContent(data: await EmbeddedResource.ReadAllAsync("travelinfo.txt"), mimeType: "text/plain") { InnerContent = "travelinfo.txt" }
];
- var fileContents = new Dictionary();
+ Dictionary fileContents = new();
foreach (BinaryContent file in files)
{
OpenAIFileReference result = await fileService.UploadContentAsync(file, new(file.InnerContent!.ToString()!, OpenAIFilePurpose.FineTune));
@@ -49,7 +49,7 @@ public async Task UploadAndRetrieveFilesAsync()
string? fileName = fileContents[fileReference.Id].InnerContent!.ToString();
ReadOnlyMemory data = content.Data ?? new();
- var typedContent = mimeType switch
+ BinaryContent typedContent = mimeType switch
{
"image/jpeg" => new ImageContent(data, mimeType) { Uri = content.Uri, InnerContent = fileName, Metadata = content.Metadata },
"audio/wav" => new AudioContent(data, mimeType) { Uri = content.Uri, InnerContent = fileName, Metadata = content.Metadata },
diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_Retrieval.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_Retrieval.cs
deleted file mode 100644
index 71acf3db0e85..000000000000
--- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_Retrieval.cs
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-using Microsoft.SemanticKernel;
-using Microsoft.SemanticKernel.Agents;
-using Microsoft.SemanticKernel.Agents.OpenAI;
-using Microsoft.SemanticKernel.ChatCompletion;
-using Microsoft.SemanticKernel.Connectors.OpenAI;
-using Resources;
-
-namespace Agents;
-
-///
-/// Demonstrate using retrieval on .
-///
-public class OpenAIAssistant_Retrieval(ITestOutputHelper output) : BaseTest(output)
-{
- ///
- /// Retrieval tool not supported on Azure OpenAI.
- ///
- protected override bool ForceOpenAI => true;
-
- [Fact]
- public async Task UseRetrievalToolWithOpenAIAssistantAgentAsync()
- {
-#pragma warning disable CS0618 // Type or member is obsolete
- OpenAIFileService fileService = new(TestConfiguration.OpenAI.ApiKey);
-
- OpenAIFileReference uploadFile =
- await fileService.UploadContentAsync(new BinaryContent(await EmbeddedResource.ReadAllAsync("travelinfo.txt")!, "text/plain"),
- new OpenAIFileUploadExecutionSettings("travelinfo.txt", OpenAIFilePurpose.Assistants));
-#pragma warning restore CS0618 // Type or member is obsolete
- // Define the agent
- OpenAIAssistantAgent agent =
- await OpenAIAssistantAgent.CreateAsync(
- kernel: new(),
- config: new(this.ApiKey, this.Endpoint),
- new()
- {
- EnableRetrieval = true, // Enable retrieval
- ModelId = this.Model,
- FileIds = [uploadFile.Id] // Associate uploaded file
- });
-
- // Create a chat for agent interaction.
- AgentGroupChat chat = new();
-
- // Respond to user input
- try
- {
- await InvokeAgentAsync("Where did sam go?");
- await InvokeAgentAsync("When does the flight leave Seattle?");
- await InvokeAgentAsync("What is the hotel contact info at the destination?");
- }
- finally
- {
- await agent.DeleteAsync();
- }
-
- // Local function to invoke agent and display the conversation messages.
- async Task InvokeAgentAsync(string input)
- {
- chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input));
-
- Console.WriteLine($"# {AuthorRole.User}: '{input}'");
-
- await foreach (ChatMessageContent content in chat.InvokeAsync(agent))
- {
- Console.WriteLine($"# {content.Role} - {content.AuthorName ?? "*"}: '{content.Content}'");
- }
- }
- }
-}
diff --git a/dotnet/samples/Concepts/Concepts.csproj b/dotnet/samples/Concepts/Concepts.csproj
index 89ac1452713a..aa303046bd36 100644
--- a/dotnet/samples/Concepts/Concepts.csproj
+++ b/dotnet/samples/Concepts/Concepts.csproj
@@ -8,7 +8,7 @@
false
true
- $(NoWarn);CS8618,IDE0009,CA1051,CA1050,CA1707,CA1054,CA2007,VSTHRD111,CS1591,RCS1110,RCS1243,CA5394,SKEXP0001,SKEXP0010,SKEXP0020,SKEXP0040,SKEXP0050,SKEXP0060,SKEXP0070,SKEXP0101,SKEXP0110
+ $(NoWarn);CS8618,IDE0009,CA1051,CA1050,CA1707,CA1054,CA2007,VSTHRD111,CS1591,RCS1110,RCS1243,CA5394,SKEXP0001,SKEXP0010,SKEXP0020,SKEXP0040,SKEXP0050,SKEXP0060,SKEXP0070,SKEXP0101,SKEXP0110,OPENAI001
Library
5ee045b0-aea3-4f08-8d31-32d1a6f8fed0
@@ -41,7 +41,10 @@
-
+
+
+ true
+
@@ -109,5 +112,8 @@
Always
+
+ Always
+
diff --git a/dotnet/samples/Concepts/Resources/Plugins/LegacyMenuPlugin.cs b/dotnet/samples/Concepts/Resources/Plugins/LegacyMenuPlugin.cs
index 7111e873cf4c..c383ea9025f1 100644
--- a/dotnet/samples/Concepts/Resources/Plugins/LegacyMenuPlugin.cs
+++ b/dotnet/samples/Concepts/Resources/Plugins/LegacyMenuPlugin.cs
@@ -7,12 +7,6 @@ namespace Plugins;
public sealed class LegacyMenuPlugin
{
- public const string CorrelationIdArgument = "correlationId";
-
- private readonly List _correlationIds = [];
-
- public IReadOnlyList CorrelationIds => this._correlationIds;
-
///
/// Returns a mock item menu.
///
@@ -20,8 +14,6 @@ public sealed class LegacyMenuPlugin
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1024:Use properties where appropriate", Justification = "Too smart")]
public string[] GetSpecials(KernelArguments? arguments)
{
- CaptureCorrelationId(arguments, nameof(GetSpecials));
-
return
[
"Special Soup: Clam Chowder",
@@ -39,8 +31,6 @@ public string GetItemPrice(
string menuItem,
KernelArguments? arguments)
{
- CaptureCorrelationId(arguments, nameof(GetItemPrice));
-
return "$9.99";
}
@@ -55,21 +45,6 @@ public bool IsItem86d(
int count,
KernelArguments? arguments)
{
- CaptureCorrelationId(arguments, nameof(IsItem86d));
-
return count < 3;
}
-
- private void CaptureCorrelationId(KernelArguments? arguments, string scope)
- {
- if (arguments?.TryGetValue(CorrelationIdArgument, out object? correlationId) ?? false)
- {
- string? correlationText = correlationId?.ToString();
-
- if (!string.IsNullOrWhiteSpace(correlationText))
- {
- this._correlationIds.Add($"{scope}:{correlationText}");
- }
- }
- }
}
diff --git a/dotnet/samples/Concepts/Resources/Plugins/MenuPlugin.cs b/dotnet/samples/Concepts/Resources/Plugins/MenuPlugin.cs
deleted file mode 100644
index be82177eda5d..000000000000
--- a/dotnet/samples/Concepts/Resources/Plugins/MenuPlugin.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-
-using System.ComponentModel;
-using Microsoft.SemanticKernel;
-
-namespace Plugins;
-
-public sealed class MenuPlugin
-{
- public const string CorrelationIdArgument = "correlationId";
-
- private readonly List _correlationIds = [];
-
- public IReadOnlyList CorrelationIds => this._correlationIds;
-
- [KernelFunction, Description("Provides a list of specials from the menu.")]
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1024:Use properties where appropriate", Justification = "Too smart")]
- public string GetSpecials()
- {
- return @"
-Special Soup: Clam Chowder
-Special Salad: Cobb Salad
-Special Drink: Chai Tea
-";
- }
-
- [KernelFunction, Description("Provides the price of the requested menu item.")]
- public string GetItemPrice(
- [Description("The name of the menu item.")]
- string menuItem)
- {
- return "$9.99";
- }
-}
diff --git a/dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj b/dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj
index decbe920b28b..df9e025b678f 100644
--- a/dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj
+++ b/dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj
@@ -9,7 +9,7 @@
true
- $(NoWarn);CS8618,IDE0009,CA1051,CA1050,CA1707,CA1054,CA2007,VSTHRD111,CS1591,RCS1110,RCS1243,CA5394,SKEXP0001,SKEXP0010,SKEXP0020,SKEXP0040,SKEXP0050,SKEXP0060,SKEXP0070,SKEXP0101,SKEXP0110
+ $(NoWarn);CS8618,IDE0009,CA1051,CA1050,CA1707,CA1054,CA2007,VSTHRD111,CS1591,RCS1110,RCS1243,CA5394,SKEXP0001,SKEXP0010,SKEXP0020,SKEXP0040,SKEXP0050,SKEXP0060,SKEXP0070,SKEXP0101,SKEXP0110,OPENAI001
Library
5ee045b0-aea3-4f08-8d31-32d1a6f8fed0
@@ -32,12 +32,16 @@
-
+
+
+ true
+
+
@@ -47,4 +51,14 @@
+
+
+ Always
+
+
+
+
+
+
+
diff --git a/dotnet/samples/GettingStartedWithAgents/README.md b/dotnet/samples/GettingStartedWithAgents/README.md
index 39952506548c..ed0e68802994 100644
--- a/dotnet/samples/GettingStartedWithAgents/README.md
+++ b/dotnet/samples/GettingStartedWithAgents/README.md
@@ -19,13 +19,17 @@ The getting started with agents examples include:
Example|Description
---|---
-[Step1_Agent](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step1_Agent.cs)|How to create and use an agent.
-[Step2_Plugins](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step2_Plugins.cs)|How to associate plug-ins with an agent.
-[Step3_Chat](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step3_Chat.cs)|How to create a conversation between agents.
-[Step4_KernelFunctionStrategies](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step4_KernelFunctionStrategies.cs)|How to utilize a `KernelFunction` as a _chat strategy_.
-[Step5_JsonResult](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step5_JsonResult.cs)|How to have an agent produce JSON.
-[Step6_DependencyInjection](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step6_DependencyInjection.cs)|How to define dependency injection patterns for agents.
-[Step7_OpenAIAssistant](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step7_OpenAIAssistant.cs)|How to create an Open AI Assistant agent.
+[Step01_Agent](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step01_Agent.cs)|How to create and use an agent.
+[Step02_Plugins](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step02_Plugins.cs)|How to associate plug-ins with an agent.
+[Step03_Chat](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step03_Chat.cs)|How to create a conversation between agents.
+[Step04_KernelFunctionStrategies](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step04_KernelFunctionStrategies.cs)|How to utilize a `KernelFunction` as a _chat strategy_.
+[Step05_JsonResult](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step05_JsonResult.cs)|How to have an agent produce JSON.
+[Step06_DependencyInjection](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step06_DependencyInjection.cs)|How to define dependency injection patterns for agents.
+[Step07_Logging](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step07_Logging.cs)|How to enable logging for agents.
+[Step08_Assistant](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step08_Assistant.cs)|How to create an Open AI Assistant agent.
+[Step09_Assistant](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step09_Assistant_Vision.cs)|How to provide an image as input to an Open AI Assistant agent.
+[Step10_Assistant](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step10_AssistantTool_CodeInterpreter_.cs)|How to use the code-interpreter tool for an Open AI Assistant agent.
+[Step11_Assistant](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithAgents/Step11_AssistantTool_FileSearch.cs)|How to use the file-search tool for an Open AI Assistant agent.
## Legacy Agents
diff --git a/dotnet/samples/GettingStartedWithAgents/Resources/cat.jpg b/dotnet/samples/GettingStartedWithAgents/Resources/cat.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..1e9f26de48fc542676a7461020206fab297c0314
GIT binary patch
literal 37831
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
X-`J9C)u%;8=#JWSR9(_<@Uj2d>mT>!jGU~c
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