diff --git a/dotnet/sample/AutoGen.OpenAI.Sample/Structural_Output.cs b/dotnet/sample/AutoGen.OpenAI.Sample/Structural_Output.cs index e562d7223a69..e83be0082bab 100644 --- a/dotnet/sample/AutoGen.OpenAI.Sample/Structural_Output.cs +++ b/dotnet/sample/AutoGen.OpenAI.Sample/Structural_Output.cs @@ -9,11 +9,10 @@ using Json.Schema; using Json.Schema.Generation; using OpenAI; -using OpenAI.Chat; namespace AutoGen.OpenAI.Sample; -internal class Structural_Output +public class Structural_Output { public static async Task RunAsync() { @@ -23,24 +22,25 @@ public static async Task RunAsync() var schemaBuilder = new JsonSchemaBuilder().FromType(); var schema = schemaBuilder.Build(); - - var personSchemaFormat = ChatResponseFormat.CreateJsonSchemaFormat( - name: "Person", - jsonSchema: BinaryData.FromObjectAsJson(schema), - description: "Person schema"); - var openAIClient = new OpenAIClient(apiKey); var openAIClientAgent = new OpenAIChatAgent( chatClient: openAIClient.GetChatClient(model), name: "assistant", - systemMessage: "You are a helpful assistant", - responseFormat: personSchemaFormat) // structural output by passing schema to response format + systemMessage: "You are a helpful assistant") .RegisterMessageConnector() .RegisterPrintMessage(); #endregion create_agent #region chat_with_agent - var reply = await openAIClientAgent.SendAsync("My name is John, I am 25 years old, and I live in Seattle. I like to play soccer and read books."); + var prompt = new TextMessage(Role.User, """ + My name is John, I am 25 years old, and I live in Seattle. I like to play soccer and read books. + """); + var reply = await openAIClientAgent.GenerateReplyAsync( + messages: [prompt], + options: new GenerateReplyOptions + { + OutputSchema = schema, + }); var person = JsonSerializer.Deserialize(reply.GetContent()); Console.WriteLine($"Name: {person.Name}"); @@ -60,31 +60,34 @@ public static async Task RunAsync() person.City.Should().Be("Seattle"); person.Hobbies.Count.Should().Be(2); } -} -#region person_class -public class Person -{ - [JsonPropertyName("name")] - [Description("Name of the person")] - [Required] - public string Name { get; set; } - - [JsonPropertyName("age")] - [Description("Age of the person")] - [Required] - public int Age { get; set; } - - [JsonPropertyName("city")] - [Description("City of the person")] - public string? City { get; set; } - - [JsonPropertyName("address")] - [Description("Address of the person")] - public string? Address { get; set; } - - [JsonPropertyName("hobbies")] - [Description("Hobbies of the person")] - public List? Hobbies { get; set; } + + #region person_class + [Title("Person")] + public class Person + { + [JsonPropertyName("name")] + [Description("Name of the person")] + [Required] + public string Name { get; set; } + + [JsonPropertyName("age")] + [Description("Age of the person")] + [Required] + public int Age { get; set; } + + [JsonPropertyName("city")] + [Description("City of the person")] + public string? City { get; set; } + + [JsonPropertyName("address")] + [Description("Address of the person")] + public string? Address { get; set; } + + [JsonPropertyName("hobbies")] + [Description("Hobbies of the person")] + public List? Hobbies { get; set; } + } + #endregion person_class + } -#endregion person_class diff --git a/dotnet/sample/AutoGen.OpenAI.Sample/Use_Json_Mode.cs b/dotnet/sample/AutoGen.OpenAI.Sample/Use_Json_Mode.cs index 392796d819fa..4e5247d93cec 100644 --- a/dotnet/sample/AutoGen.OpenAI.Sample/Use_Json_Mode.cs +++ b/dotnet/sample/AutoGen.OpenAI.Sample/Use_Json_Mode.cs @@ -4,13 +4,12 @@ using System.Text.Json; using System.Text.Json.Serialization; using AutoGen.Core; -using AutoGen.OpenAI; using AutoGen.OpenAI.Extension; using FluentAssertions; using OpenAI; using OpenAI.Chat; -namespace AutoGen.BasicSample; +namespace AutoGen.OpenAI.Sample; public class Use_Json_Mode { @@ -50,18 +49,20 @@ public static async Task RunAsync() person.Age.Should().Be(25); person.Address.Should().BeNullOrEmpty(); } -} -#region person_class -public class Person -{ - [JsonPropertyName("name")] - public string Name { get; set; } - [JsonPropertyName("age")] - public int Age { get; set; } + #region person_class + public class Person + { + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("age")] + public int Age { get; set; } - [JsonPropertyName("address")] - public string Address { get; set; } + [JsonPropertyName("address")] + public string Address { get; set; } + } + #endregion person_class } -#endregion person_class + diff --git a/dotnet/src/AutoGen.Core/Agent/IAgent.cs b/dotnet/src/AutoGen.Core/Agent/IAgent.cs index 34a31055d1bf..f2b8ce67d01b 100644 --- a/dotnet/src/AutoGen.Core/Agent/IAgent.cs +++ b/dotnet/src/AutoGen.Core/Agent/IAgent.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Json.Schema; namespace AutoGen.Core; @@ -42,6 +43,7 @@ public GenerateReplyOptions(GenerateReplyOptions other) this.MaxToken = other.MaxToken; this.StopSequence = other.StopSequence?.Select(s => s)?.ToArray(); this.Functions = other.Functions?.Select(f => f)?.ToArray(); + this.OutputSchema = other.OutputSchema; } public float? Temperature { get; set; } @@ -51,4 +53,9 @@ public GenerateReplyOptions(GenerateReplyOptions other) public string[]? StopSequence { get; set; } public FunctionContract[]? Functions { get; set; } + + /// + /// Structural schema for the output. This property only applies to certain LLMs. + /// + public JsonSchema? OutputSchema { get; set; } } diff --git a/dotnet/src/AutoGen.OpenAI/Agent/OpenAIChatAgent.cs b/dotnet/src/AutoGen.OpenAI/Agent/OpenAIChatAgent.cs index 1ae1e45db155..44711c5e4289 100644 --- a/dotnet/src/AutoGen.OpenAI/Agent/OpenAIChatAgent.cs +++ b/dotnet/src/AutoGen.OpenAI/Agent/OpenAIChatAgent.cs @@ -10,6 +10,7 @@ using AutoGen.OpenAI.Extension; using global::OpenAI; using global::OpenAI.Chat; +using Json.Schema; namespace AutoGen.OpenAI; @@ -179,6 +180,14 @@ private ChatCompletionOptions CreateChatCompletionsOptions(GenerateReplyOptions? } } + if (options?.OutputSchema is not null) + { + option.ResponseFormat = ChatResponseFormat.CreateJsonSchemaFormat( + name: options.OutputSchema.GetTitle() ?? throw new ArgumentException("Output schema must have a title"), + jsonSchema: BinaryData.FromObjectAsJson(options.OutputSchema), + description: options.OutputSchema.GetDescription()); + } + return option; } diff --git a/dotnet/test/AutoGen.OpenAI.Tests/AutoGen.OpenAI.Tests.csproj b/dotnet/test/AutoGen.OpenAI.Tests/AutoGen.OpenAI.Tests.csproj index a6495fc4487c..d1e48686007c 100644 --- a/dotnet/test/AutoGen.OpenAI.Tests/AutoGen.OpenAI.Tests.csproj +++ b/dotnet/test/AutoGen.OpenAI.Tests/AutoGen.OpenAI.Tests.csproj @@ -12,6 +12,7 @@ + diff --git a/dotnet/test/AutoGen.OpenAI.Tests/OpenAIChatAgentTest.cs b/dotnet/test/AutoGen.OpenAI.Tests/OpenAIChatAgentTest.cs index bcbfee6e208a..04f4d3d4d393 100644 --- a/dotnet/test/AutoGen.OpenAI.Tests/OpenAIChatAgentTest.cs +++ b/dotnet/test/AutoGen.OpenAI.Tests/OpenAIChatAgentTest.cs @@ -246,7 +246,6 @@ public async Task ItCreateOpenAIChatAgentWithChatCompletionOptionAsync() respond.GetContent()?.Should().NotBeNullOrEmpty(); } - private OpenAIClient CreateOpenAIClientFromAzureOpenAI() { var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new Exception("Please set AZURE_OPENAI_ENDPOINT environment variable."); diff --git a/dotnet/test/AutoGen.OpenAI.Tests/OpenAISampleTest.cs b/dotnet/test/AutoGen.OpenAI.Tests/OpenAISampleTest.cs new file mode 100644 index 000000000000..6376c4ff4986 --- /dev/null +++ b/dotnet/test/AutoGen.OpenAI.Tests/OpenAISampleTest.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// OpenAISampleTest.cs + +using System; +using System.IO; +using System.Threading.Tasks; +using AutoGen.OpenAI.Sample; +using AutoGen.Tests; +using Xunit.Abstractions; + +namespace AutoGen.OpenAI.Tests; + +public class OpenAISampleTest +{ + private readonly ITestOutputHelper _output; + + public OpenAISampleTest(ITestOutputHelper output) + { + _output = output; + Console.SetOut(new ConsoleWriter(_output)); + } + + [ApiKeyFact("OPENAI_API_KEY")] + public async Task Structural_OutputAsync() + { + await Structural_Output.RunAsync(); + } + + [ApiKeyFact("OPENAI_API_KEY")] + public async Task Use_Json_ModeAsync() + { + await Use_Json_Mode.RunAsync(); + } + + public class ConsoleWriter : StringWriter + { + private ITestOutputHelper output; + public ConsoleWriter(ITestOutputHelper output) + { + this.output = output; + } + + public override void WriteLine(string? m) + { + output.WriteLine(m); + } + } +}