{"id":3461,"date":"2024-10-01T12:12:51","date_gmt":"2024-10-01T19:12:51","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/semantic-kernel\/?p=3461"},"modified":"2024-11-11T12:33:19","modified_gmt":"2024-11-11T20:33:19","slug":"using-json-schema-for-structured-output-in-net-for-openai-models","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/agent-framework\/using-json-schema-for-structured-output-in-net-for-openai-models\/","title":{"rendered":"Using JSON Schema for Structured Output in .NET for OpenAI Models"},"content":{"rendered":"<p>In one of the previous posts, we demonstrated how to use JSON Schema to get Structured Output with OpenAI and Python version of Semantic Kernel: <a href=\"https:\/\/devblogs.microsoft.com\/semantic-kernel\/using-json-schema-for-structured-output-in-python-for-openai-models\/\" target=\"_blank\" rel=\"noopener\">Using JSON Schema for Structured Output in Python for OpenAI Models<\/a>.<\/p>\n<p>In this post, we will explore how to implement a JSON Schema-based structured output using .NET version of Semantic Kernel.<\/p>\n<p>For more information on structured outputs with OpenAI, visit their official guide: <a href=\"https:\/\/platform.openai.com\/docs\/guides\/structured-outputs\/introduction\" target=\"_blank\" rel=\"noopener\">OpenAI Structured Outputs Guide<\/a>.<\/p>\n<h3>Why JSON Schema?<\/h3>\n<p>When interacting with AI models, especially in scenarios where consistency, clarity, and accuracy are important (such as tutoring or solving complex problems), the output must be predictable. JSON Schema ensures that responses are well-structured, follow a specified format, and can be easily deserialized by the system. This structure is key when building applications that rely on a specific format for further processing.<\/p>\n<h3>Supported Models for Structured Outputs<\/h3>\n<h4>Azure OpenAI:<\/h4>\n<ul>\n<li>Access to <code>gpt-4o-2024-08-06<\/code><\/li>\n<li>The <code>2024-08-01-preview<\/code> API version<\/li>\n<li>See more information <a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/ai-services\/openai\/how-to\/structured-outputs\" target=\"_blank\" rel=\"noopener\">here<\/a>.<\/li>\n<\/ul>\n<h4>OpenAI:<\/h4>\n<ul>\n<li><code>gpt-4o-mini-2024-07-18<\/code> and later<\/li>\n<li><code>gpt-4o-2024-08-06<\/code> and later<\/li>\n<li>See more information <a href=\"https:\/\/platform.openai.com\/docs\/guides\/structured-outputs\/supported-models\" target=\"_blank\" rel=\"noopener\">here<\/a>.<\/li>\n<\/ul>\n<p>In this example, we will use OpenAI <code>gpt-4o-2024-08-06<\/code> model.<\/p>\n<h3>Structured Outputs with <code class=\"language-cs language-csharp\">OpenAI.Chat.ChatResponseFormat<\/code><\/h3>\n<p>One of the approaches how to provide JSON Schema to OpenAI model with .NET version of Semantic Kernel is to initialize <code class=\"language-cs language-csharp\">OpenAI.Chat.ChatResponseFormat<\/code> object and set it in <code class=\"language-cs language-csharp\">OpenAIPromptExecutionSettings.ResponseFormat<\/code> property.<\/p>\n<p>Let&#8217;s try to get a top 10 movies of all time from the model and specify JSON Schema of the desired response:<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">\/\/ Initialize kernel.\r\nKernel kernel = Kernel.CreateBuilder()\r\n    .AddOpenAIChatCompletion(\r\n        modelId: \"gpt-4o-2024-08-06\",\r\n        apiKey: Environment.GetEnvironmentVariable(\"OpenAI__ApiKey\"))\r\n    .Build();\r\n\r\n\/\/ Initialize ChatResponseFormat object with JSON schema of desired response format.\r\nChatResponseFormat chatResponseFormat = ChatResponseFormat.CreateJsonSchemaFormat(\r\n    jsonSchemaFormatName: \"movie_result\",\r\n    jsonSchema: BinaryData.FromString(\"\"\"\r\n        {\r\n            \"type\": \"object\",\r\n            \"properties\": {\r\n                \"Movies\": {\r\n                    \"type\": \"array\",\r\n                    \"items\": {\r\n                        \"type\": \"object\",\r\n                        \"properties\": {\r\n                            \"Title\": { \"type\": \"string\" },\r\n                            \"Director\": { \"type\": \"string\" },\r\n                            \"ReleaseYear\": { \"type\": \"integer\" },\r\n                            \"Rating\": { \"type\": \"number\" },\r\n                            \"IsAvailableOnStreaming\": { \"type\": \"boolean\" },\r\n                            \"Tags\": { \"type\": \"array\", \"items\": { \"type\": \"string\" } }\r\n                        },\r\n                        \"required\": [\"Title\", \"Director\", \"ReleaseYear\", \"Rating\", \"IsAvailableOnStreaming\", \"Tags\"],\r\n                        \"additionalProperties\": false\r\n                    }\r\n                }\r\n            },\r\n            \"required\": [\"Movies\"],\r\n            \"additionalProperties\": false\r\n        }\r\n        \"\"\"),\r\n    jsonSchemaIsStrict: true);\r\n\r\n\/\/ Specify response format by setting ChatResponseFormat object in prompt execution settings.\r\nvar executionSettings = new OpenAIPromptExecutionSettings\r\n{\r\n    ResponseFormat = chatResponseFormat\r\n};\r\n\r\n\/\/ Send a request and pass prompt execution settings with desired response format.\r\nvar result = await kernel.InvokePromptAsync(\"What are the top 10 movies of all time?\", new(executionSettings));\r\n\r\nConsole.WriteLine(result);\r\n<\/code><\/pre>\n<p>In this approach, we define the JSON Schema from string and pass it to <code class=\"language-cs language-csharp\">ChatResponseFormat.CreateJsonSchemaFormat<\/code> method. Then we take newly created <code class=\"language-cs language-csharp\">ChatResponseFormat<\/code> and set it in <code class=\"language-cs language-csharp\">OpenAIPromptExecutionSettings.ResponseFormat<\/code> property. As a result, we get a string in JSON format which we can output to console:<\/p>\n<p>Output:<\/p>\n<pre><code>{\r\n    \"Movies\": [\r\n        {\r\n            \"Title\": \"The Shawshank Redemption\",\r\n            \"Director\": \"Frank Darabont\",\r\n            \"ReleaseYear\": 1994,\r\n            \"Rating\": 9.3,\r\n            \"IsAvailableOnStreaming\": true,\r\n            \"Tags\": [\r\n                \"Drama\"\r\n            ]\r\n        }\r\n        \/\/ and more...\r\n    ]\r\n}\r\n<\/code><\/pre>\n<p>Because we provided JSON Schema for desired response format, we can be sure that this format will be the same in all AI model responses, which means that we can define a class with all of movie properties and deserialize the response to access each property in the code:<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">\/\/ Define response models\r\npublic class MovieResult\r\n{\r\n    public List&lt;Movie&gt; Movies { get; set; }\r\n}\r\n\r\npublic class Movie\r\n{\r\n    public string Title { get; set; }\r\n\r\n    public string Director { get; set; }\r\n\r\n    public int ReleaseYear { get; set; }\r\n\r\n    public double Rating { get; set; }\r\n\r\n    public bool IsAvailableOnStreaming { get; set; }\r\n\r\n    public List&lt;string&gt; Tags { get; set; }\r\n}\r\n\r\n\/\/ Send a request and pass prompt execution settings with desired response format.\r\nvar result = await kernel.InvokePromptAsync(\"What are the top 10 movies of all time?\", new(executionSettings));\r\n\r\n\/\/ Deserialize string response to a strong type to access type properties.\r\n\/\/ At this point, the deserialization logic won't fail, because MovieResult type was specified as desired response format.\r\n\/\/ This ensures that response string is a serialized version of MovieResult type.\r\nvar movieResult = JsonSerializer.Deserialize&lt;MovieResult&gt;(result.ToString());\r\n<\/code><\/pre>\n<p>This approach is flexible, since it allows to specify each property and its type within a string, which means that JSON schemas can be stored in separate files, for example, for reusability. On the other hand, an information about response format should be specified in two places &#8211; <code class=\"language-cs language-csharp\">ChatResponseFormat<\/code> object and then the class which we want to use for deserialization purposes. This can be improved with the following approach.<\/p>\n<h3>Structured Outputs with <code class=\"language-cs language-csharp\">System.Type<\/code><\/h3>\n<p>This approach allows to define a desired response format using C# class or structure and then set its type directly in <code class=\"language-cs language-csharp\">OpenAIPromptExecutionSettings.ResponseFormat<\/code>. In this case, JSON schema generation will be performed automatically by Semantic Kernel. Let&#8217;s use the same <code class=\"language-cs language-csharp\">MovieResult<\/code> and <code class=\"language-cs language-csharp\">Movie<\/code> classes defined in previous example:<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">\/\/ Initialize kernel.\r\nKernel kernel = Kernel.CreateBuilder()\r\n    .AddOpenAIChatCompletion(\r\n        modelId: \"gpt-4o-2024-08-06\",\r\n        apiKey: Environment.GetEnvironmentVariable(\"OpenAI__ApiKey\"))\r\n    .Build();\r\n\r\n\/\/ Specify response format by setting Type object in prompt execution settings.\r\nvar executionSettings = new OpenAIPromptExecutionSettings\r\n{\r\n    ResponseFormat = typeof(MovieResult)\r\n};\r\n\r\n\/\/ Send a request and pass prompt execution settings with desired response format.\r\nvar result = await kernel.InvokePromptAsync(\"What are the top 10 movies of all time?\", new(executionSettings));\r\n\r\n\/\/ Deserialize string response to a strong type to access type properties.\r\n\/\/ At this point, the deserialization logic won't fail, because MovieResult type was specified as desired response format.\r\n\/\/ This ensures that response string is a serialized version of MovieResult type.\r\nvar movieResult = JsonSerializer.Deserialize&lt;MovieResult&gt;(result.ToString());\r\n\r\n\/\/ Output the result\r\nfor (var i = 0; i &lt; movieResult.Movies.Count; i++)\r\n{\r\n    var movie = movieResult.Movies[i];\r\n\r\n    Console.WriteLine($\"Movie #{i + 1}\");\r\n    Console.WriteLine($\"Title: {movie.Title}\");\r\n    Console.WriteLine($\"Director: {movie.Director}\");\r\n    Console.WriteLine($\"Release year: {movie.ReleaseYear}\");\r\n    Console.WriteLine($\"Rating: {movie.Rating}\");\r\n    Console.WriteLine($\"Is available on streaming: {movie.IsAvailableOnStreaming}\");\r\n    Console.WriteLine($\"Tags: {string.Join(\",\", movie.Tags)}\");\r\n}\r\n<\/code><\/pre>\n<p>Output:<\/p>\n<pre>Movie #1\r\nTitle: The Shawshank Redemption\r\nDirector: Frank Darabont\r\nRelease year: 1994\r\nRating: 9.3\r\nIs available on streaming: True\r\nTags: Drama\r\n\/\/ and more...\r\n<\/pre>\n<p>With this approach, it&#8217;s possible to define desired response format once using C# class or structure and use the same type to deserialize model response and work with its properties. In this case, Semantic Kernel generates JSON Schema automatically based on provided type.<\/p>\n<h4>Limitations<\/h4>\n<p>Some keywords from JSON Schema are not supported in OpenAI Structured Outputs yet. For example, <code>format<\/code> keyword for strings is not supported. It means that JSON Schema won&#8217;t be accepted when response format type contains properties with types <code>DateTime<\/code>, <code>DateTimeOffset<\/code>, <code>DateOnly<\/code>, <code>TimeSpan<\/code>, <code>TimeOnly<\/code>, <code>Uri<\/code>. One of the workarounds would be to define these properties as strings.<\/p>\n<h3>Structured Outputs with Function Calling<\/h3>\n<p>Desired response format can be also configured as a final response format in Function Calling loop. Let&#8217;s define a plugin which returns a collection of email body messages:<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">\/\/ Define plugin\r\npublic sealed class EmailPlugin\r\n{\r\n    [KernelFunction]\r\n    public List&lt;string&gt; GetEmails()\r\n    {\r\n        return\r\n        [\r\n            \"Hey, just checking in to see how you're doing!\",\r\n            \"Can you pick up some groceries on your way back home? We need milk and bread.\",\r\n            \"Happy Birthday! Wishing you a fantastic day filled with love and joy.\",\r\n            \"Let's catch up over coffee this Saturday. It's been too long!\",\r\n            \"Please review the attached document and provide your feedback by EOD.\",\r\n        ];\r\n    }\r\n}\r\n<\/code><\/pre>\n<p>We also need to define a type of desired response format:<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">\/\/ Define response format types\r\npublic sealed class EmailResult\r\n{\r\n    public List&lt;Email&gt; Emails { get; set; }\r\n}\r\n\r\npublic sealed class Email\r\n{\r\n    public string Body { get; set; }\r\n\r\n    public string Category { get; set; }\r\n}\r\n<\/code><\/pre>\n<p>Now, we can specify a response format, enable automatic function calling and send a request:<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">\/\/ Initialize kernel.\r\nKernel kernel = Kernel.CreateBuilder()\r\n    .AddOpenAIChatCompletion(\r\n        modelId: \"gpt-4o-2024-08-06\",\r\n        apiKey: Environment.GetEnvironmentVariable(\"OpenAI__ApiKey\"))\r\n    .Build();\r\n\r\n\/\/ Import plugin\r\nkernel.ImportPluginFromType&lt;EmailPlugin&gt;();\r\n\r\nvar executionSettings = new OpenAIPromptExecutionSettings\r\n{\r\n    ResponseFormat = typeof(EmailResult), \/\/ Specify response format\r\n    FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() \/\/ Enable automatic function calling\r\n};\r\n\r\n\/\/ Send a request and pass prompt execution settings with desired response format.\r\nvar result = await kernel.InvokePromptAsync(\"Process the emails.\", new(executionSettings));\r\n\r\n\/\/ Deserialize string response to a strong type to access type properties.\r\n\/\/ At this point, the deserialization logic won't fail, because EmailResult type was specified as desired response format.\r\n\/\/ This ensures that response string is a serialized version of EmailResult type.\r\nvar emailResult = JsonSerializer.Deserialize&lt;EmailResult&gt;(result.ToString());\r\n\r\nfor (var i = 0; i &lt; emailResult.Emails.Count; i++)\r\n{\r\n    var email = emailResult.Emails[i];\r\n\r\n    Console.WriteLine($\"Email #{i + 1}\");\r\n    Console.WriteLine($\"Body: {email.Body}\");\r\n    Console.WriteLine($\"Category: {email.Category}\");\r\n}\r\n<\/code><\/pre>\n<p>Note that the prompt is as simple as <code>Process the emails<\/code>. We don&#8217;t specify how exactly we want AI model to process the emails, but as response format we want to get two properties in <code>Email<\/code> type &#8211; <code>Body<\/code> and <code>Category<\/code>. In this way, we can control model&#8217;s behavior by using response format instead of user\/system prompt.<\/p>\n<p>Output:<\/p>\n<pre>Email #1\r\nBody: Hey, just checking in to see how you're doing!\r\nCategory: Personal\r\nEmail #2\r\nBody: Can you pick up some groceries on your way back home? We need milk and bread.\r\nCategory: Reminder\r\nEmail #3\r\nBody: Happy Birthday! Wishing you a fantastic day filled with love and joy.\r\nCategory: Personal\r\nEmail #4\r\nBody: Let's catch up over coffee this Saturday. It's been too long!\r\nCategory: Meeting\r\nEmail #5\r\nBody: Please review the attached document and provide your feedback by EOD.\r\nCategory: Work\r\n<\/pre>\n<h2>Conclusion<\/h2>\n<p>Using JSON Schema and a framework like Semantic Kernel allows you to control the format of AI-generated responses, ensuring that the output is structured, predictable, and easy to use. This .NET approach is particularly useful for applications that require consistent and well-structured output, such as educational tools or automated systems.<\/p>\n<p>Please reach out if you have any questions or feedback through our\u00a0<a href=\"https:\/\/github.com\/microsoft\/semantic-kernel\/discussions\/categories\/general\" target=\"_blank\" rel=\"noopener\">Semantic Kernel GitHub Discussion Channel<\/a>. We look forward to hearing from you! We would also love your support &#8212; if you\u2019ve enjoyed using Semantic Kernel, give us a star on <a href=\"https:\/\/github.com\/microsoft\/semantic-kernel\" target=\"_blank\" rel=\"noopener\">GitHub<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In one of the previous posts, we demonstrated how to use JSON Schema to get Structured Output with OpenAI and Python version of Semantic Kernel: Using JSON Schema for Structured Output in Python for OpenAI Models. In this post, we will explore how to implement a JSON Schema-based structured output using .NET version of Semantic [&hellip;]<\/p>\n","protected":false},"author":149071,"featured_media":2311,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[78,47],"tags":[79,48,63,9],"class_list":["post-3461","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-net","category-announcement","tag-net","tag-ai","tag-microsoft-semantic-kernel","tag-semantic-kernel"],"acf":[],"blog_post_summary":"<p>In one of the previous posts, we demonstrated how to use JSON Schema to get Structured Output with OpenAI and Python version of Semantic Kernel: Using JSON Schema for Structured Output in Python for OpenAI Models. In this post, we will explore how to implement a JSON Schema-based structured output using .NET version of Semantic [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/posts\/3461","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/users\/149071"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/comments?post=3461"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/posts\/3461\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/media\/2311"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/media?parent=3461"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/categories?post=3461"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/tags?post=3461"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}