From 19ede2f9a4a26383bc4b5c894f0c4e6ed14e6bef Mon Sep 17 00:00:00 2001 From: XiaoYun Zhang Date: Thu, 29 Aug 2024 14:50:51 -0700 Subject: [PATCH 1/2] add output schema --- .../Structural_Output.cs | 77 ++++++++++--------- .../AutoGen.OpenAI.Sample/Use_Json_Mode.cs | 27 +++---- dotnet/src/AutoGen.Core/Agent/IAgent.cs | 7 ++ .../AutoGen.OpenAI/Agent/OpenAIChatAgent.cs | 9 +++ .../AutoGen.OpenAI.Tests.csproj | 1 + .../OpenAIChatAgentTest.cs | 1 - .../AutoGen.OpenAI.Tests/OpenAISampleTest.cs | 48 ++++++++++++ 7 files changed, 119 insertions(+), 51 deletions(-) create mode 100644 dotnet/test/AutoGen.OpenAI.Tests/OpenAISampleTest.cs 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..fba6f7786cc3 --- /dev/null +++ b/dotnet/test/AutoGen.OpenAI.Tests/OpenAISampleTest.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// BasicSampleTest.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); + } + } +} From d574f3712d3e0403a2a562e1b878766fb51f8a84 Mon Sep 17 00:00:00 2001 From: XiaoYun Zhang Date: Thu, 29 Aug 2024 16:26:35 -0700 Subject: [PATCH 2/2] fix format --- dotnet/test/AutoGen.OpenAI.Tests/OpenAISampleTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/test/AutoGen.OpenAI.Tests/OpenAISampleTest.cs b/dotnet/test/AutoGen.OpenAI.Tests/OpenAISampleTest.cs index fba6f7786cc3..6376c4ff4986 100644 --- a/dotnet/test/AutoGen.OpenAI.Tests/OpenAISampleTest.cs +++ b/dotnet/test/AutoGen.OpenAI.Tests/OpenAISampleTest.cs @@ -1,5 +1,5 @@ // Copyright (c) Microsoft Corporation. All rights reserved. -// BasicSampleTest.cs +// OpenAISampleTest.cs using System; using System.IO;