Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[.Net] add output schema to generateReplyOption #3450

Merged
merged 2 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 40 additions & 37 deletions dotnet/sample/AutoGen.OpenAI.Sample/Structural_Output.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand All @@ -23,24 +22,25 @@ public static async Task RunAsync()

var schemaBuilder = new JsonSchemaBuilder().FromType<Person>();
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<Person>(reply.GetContent());
Console.WriteLine($"Name: {person.Name}");
Expand All @@ -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<string>? 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<string>? Hobbies { get; set; }
}
#endregion person_class

}
#endregion person_class
27 changes: 14 additions & 13 deletions dotnet/sample/AutoGen.OpenAI.Sample/Use_Json_Mode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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

7 changes: 7 additions & 0 deletions dotnet/src/AutoGen.Core/Agent/IAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Json.Schema;

namespace AutoGen.Core;

Expand Down Expand Up @@ -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; }
Expand All @@ -51,4 +53,9 @@ public GenerateReplyOptions(GenerateReplyOptions other)
public string[]? StopSequence { get; set; }

public FunctionContract[]? Functions { get; set; }

/// <summary>
/// Structural schema for the output. This property only applies to certain LLMs.
/// </summary>
public JsonSchema? OutputSchema { get; set; }
}
9 changes: 9 additions & 0 deletions dotnet/src/AutoGen.OpenAI/Agent/OpenAIChatAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using AutoGen.OpenAI.Extension;
using global::OpenAI;
using global::OpenAI.Chat;
using Json.Schema;

namespace AutoGen.OpenAI;

Expand Down Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\sample\AutoGen.OpenAI.Sample\AutoGen.OpenAI.Sample.csproj" />
<ProjectReference Include="..\..\src\AutoGen.OpenAI\AutoGen.OpenAI.csproj" />
<ProjectReference Include="..\..\src\AutoGen.SourceGenerator\AutoGen.SourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\AutoGen.Test.Share\AutoGen.Tests.Share.csproj" />
Expand Down
1 change: 0 additions & 1 deletion dotnet/test/AutoGen.OpenAI.Tests/OpenAIChatAgentTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
Expand Down
48 changes: 48 additions & 0 deletions dotnet/test/AutoGen.OpenAI.Tests/OpenAISampleTest.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Loading