Skip to content

Commit ee8f87e

Browse files
committed
✏️ Update multiple files for clarity and structure
- Updated content in Index.razor, SiteMap.cs, and sitemap.xml. - Enhanced readability and organization across files. - Aimed to improve maintainability and user understanding. Generated by Copilot
1 parent 0292111 commit ee8f87e

File tree

4 files changed

+77
-87
lines changed

4 files changed

+77
-87
lines changed

TestArena/Blog/AI/OpenAI/openai-rest-api/Index.razor

Lines changed: 56 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,16 @@
44

55
@using TestArena
66
@inject OpenAIService OpenAIService
7-
87
@code {
98
PageInfo currentPage = SiteMap.Pages.FirstOrDefault(x => x.RelativePath == "/blog/ai/openai-rest-api/structured-output")!;
109

11-
// Demo state for structured recipe output
12-
private global::OpenAIService.Recipe? recipe = null;
13-
private string dishInput = "Butter Chicken";
10+
// Demo state for structured movie output
11+
private global::OpenAIService.MovieDetails? movieDetails = null;
12+
private string movieInput = "The Matrix";
1413
private string apiKeyInput = string.Empty;
1514
private string ApiKeyInputTrimmed => apiKeyInput?.Trim() ?? string.Empty;
16-
private string DishInputTrimmed => dishInput?.Trim() ?? string.Empty;
17-
private bool IsGetRecipeDisabled => isLoading || string.IsNullOrWhiteSpace(ApiKeyInputTrimmed) || string.IsNullOrWhiteSpace(DishInputTrimmed);
15+
private string MovieInputTrimmed => movieInput?.Trim() ?? string.Empty;
16+
private bool IsGetMovieDisabled => isLoading || string.IsNullOrWhiteSpace(ApiKeyInputTrimmed) || string.IsNullOrWhiteSpace(MovieInputTrimmed);
1817
private bool isApiKeyVisible = false;
1918
private bool isLoading = false;
2019

@@ -23,22 +22,22 @@
2322
// Optionally, prefill API key from env or local storage if desired
2423
}
2524

26-
private async Task GetRecipe()
25+
private async Task GetMovieDetails()
2726
{
2827
isLoading = true;
29-
recipe = null;
28+
movieDetails = null;
3029
StateHasChanged();
3130

3231
try
3332
{
34-
// Call the CreateRecipeAsync method which returns a structured Recipe object
35-
recipe = await OpenAIService.CreateRecipeAsync(DishInputTrimmed, ApiKeyInputTrimmed);
33+
// Call the CreateMovieDetailsAsync method which returns a structured MovieDetails object
34+
movieDetails = await OpenAIService.CreateMovieDetailsAsync(MovieInputTrimmed, ApiKeyInputTrimmed);
3635
}
3736
catch (Exception ex)
3837
{
3938
// Show error as a simple serialized message in the UI (keep demo simple)
40-
recipe = null;
41-
Console.WriteLine($"CreateRecipeAsync failed: {ex.Message}");
39+
movieDetails = null;
40+
Console.WriteLine($"CreateMovieDetailsAsync failed: {ex.Message}");
4241
}
4342

4443
isLoading = false;
@@ -56,9 +55,10 @@
5655

5756
<Section Heading="Understanding OpenAI APIs" Level="2">
5857
<p>
59-
Imagine you're building a smart home system. Just as your system needs to communicate with various devices through specific protocols,
60-
developers need to interact with AI models through APIs. OpenAI's APIs serve as this communication bridge, allowing you to harness
61-
the power of advanced AI models like GPT-4 in your applications.
58+
When integrating AI into real apps you don't just want creative prose — you want predictable, machine-readable data you can act on.
59+
"Structured output" means asking the model to return a defined JSON shape (or similar) so your code can reliably parse it and wire it
60+
into your UI, database or business logic. This page walks through how to design prompts, use function schemas, and parse results safely
61+
so you get deterministic, structured responses from OpenAI APIs.
6262
</p>
6363
</Section>
6464

@@ -83,7 +83,7 @@
8383

8484
<Section Heading="When to Use OpenAI APIs?" Level="4">
8585
<p>
86-
Wondering when OpenAI APIs can make a real difference? Here are some moments where they shine—and you’ll probably recognize a few from your own life or work:
86+
Wondering when OpenAI APIs can make a real difference? Here are some moments where they shine—and where structured output is especially helpful:
8787
</p>
8888
<ul>
8989
<li><b>Stuck on a blank page?</b> Instantly generate blog intros, catchy headlines, or even code snippets to kickstart your creativity.</li>
@@ -92,6 +92,7 @@
9292
<li><b>Dreaming up new designs?</b> Use AI to brainstorm product ideas, generate marketing copy, or even create images for your next campaign.</li>
9393
<li><b>Making your app accessible?</b> Transcribe audio, translate languages, or simplify complex text so everyone can use your product.</li>
9494
<li><b>Personalized learning or coaching?</b> Build a tutor that adapts to each student’s needs, explains tough concepts, or quizzes them in a fun way.</li>
95+
<li><b>APIs and integrations:</b> When you need the model to return structured data—product specs, recipe objects, or event definitions—use structured output to avoid brittle text parsing.</li>
9596
</ul>
9697
<p>
9798
If you’ve ever wished for a superpower to automate, accelerate, or amplify your work—OpenAI APIs are your toolkit. The best part? You don’t need to be an AI expert to get started!
@@ -147,56 +148,31 @@ public class OpenAIClient
147148
</CodeSnippet>
148149

149150

150-
<h4>3. Handling Chat Conversations (Chat Completion API)</h4>
151+
<h4>3. Structured output: system prompts, function schemas, and parsing</h4>
151152
<p>
152-
To have a conversation with an AI model—such as asking it to summarize a movie—you'll use the <b>Chat Completions</b> endpoint. This endpoint lets you send a series of messages (like a chat history) and receive a smart, context-aware response from the model.
153-
154-
The request body for this endpoint typically includes:
155-
<ul>
156-
<li>
157-
<b>model</b>: The name of the AI model you want to use (e.g., <code>gpt-4</code> or <code>gpt-3.5-turbo</code>). Different models have different capabilities and costs.
158-
</li>
159-
<li>
160-
<b>messages</b>: An array of message objects representing the conversation so far. Each message has a <code>role</code> (such as <code>system</code>, <code>user</code>, or <code>assistant</code>) and <code>content</code> (the actual text). This structure allows the model to understand context and respond appropriately.
161-
</li>
162-
<li>
163-
<b>max_tokens</b>: The maximum number of tokens (words or word pieces) in the response. This helps control the length and cost of the output.
164-
</li>
165-
<li>
166-
<b>Other optional parameters</b>: You can also specify things like <code>temperature</code> (controls randomness/creativity), <code>top_p</code> (controls diversity), and more, depending on your needs.
167-
</li>
168-
</ul>
169-
Here’s how you might use it in practice:
153+
If you want machine-readable results, design for structured output. There are three main techniques that work well together:
170154
</p>
171-
<p><b>Example prompt:</b> <code>Give me a summary of the movie Inception.</code></p>
172-
<CodeSnippet Language="csharp">
173-
public async Task&lt;string&gt; GetChatResponseAsync()
174-
{
175-
var url = "https://api.openai.com/v1/chat/completions";
176-
var requestBody = new
177-
{
178-
model = "gpt-4",
179-
messages = new[]
180-
{
181-
new { role = "system", content = "You are a helpful assistant." },
182-
new { role = "user", content = "Give me a summary of the movie Inception." }
183-
},
184-
max_tokens = 150
185-
};
186-
var json = JsonSerializer.Serialize(requestBody);
187-
var request = CreateRequest(url, HttpMethod.Post, json);
155+
<ol>
156+
<li><b>System prompt constraints</b> — tell the model to respond only with a JSON object and describe the exact fields and limits (max length, array sizes). A clear system message reduces ambiguity.</li>
157+
<li><b>Function schema / structured definitions</b> — use the API's function schema (name + JSON Schema) to signal the desired structure. The model may return the object as a <code>function_call</code>, with the structured payload in <code>function_call.arguments</code>.</li>
158+
<li><b>Parsing & validation</b> — never assume perfect output. Prefer <code>function_call.arguments</code> when present, strip surrounding text or fences, parse JSON safely, and enforce length / item limits in your code.</li>
159+
</ol>
188160

189-
var response = await _httpClient.SendAsync(request);
190-
response.EnsureSuccessStatusCode();
161+
<p><b>Practical example — generating a movie details object</b></p>
162+
<p>
163+
The demo on this page uses a helper method <code>CreateMovieDetailsAsync</code> that follows this pattern: it sends a system instruction that requests a single JSON object with fields like <code>title</code>, <code>year</code>, <code>director</code>, <code>description</code>, <code>genres</code> and <code>actors</code>. It also provides a function schema describing types and limits. When the response arrives the method prefers <code>function_call.arguments</code> (if the model used a function call), extracts the JSON, and then trims and validates the fields before returning a typed <code>MovieDetails</code> record to the app.
164+
</p>
191165

192-
var responseString = await response.Content.ReadAsStringAsync();
193-
using var doc = JsonDocument.Parse(responseString);
194-
return doc.RootElement.GetProperty("choices")[0].GetProperty("message").GetProperty("content").GetString();
195-
}
196-
// Usage:
197-
// string summary = await GetChatResponseAsync();
166+
<CodeSnippet Language="csharp">// High-level contract for CreateMovieDetailsAsync
167+
// Input: movie name + api key
168+
// Output: MovieDetails record with: Title, Year, Director, Description, Genres[], Actors[]
169+
// Error modes: returns null on parse failure or empty/invalid response
198170
</CodeSnippet>
199171

172+
<p>
173+
This approach gives you the best of both worlds: the model's language intelligence plus predictable structured data you can render and store safely.
174+
</p>
175+
200176
</Section>
201177

202178
<Section Heading="�️ Demo: Structured Recipe Generator" Level="3">
@@ -213,61 +189,62 @@ public async Task&lt;string&gt; GetChatResponseAsync()
213189
</div>
214190
</div>
215191
<div class="mb-3">
216-
<label for="dishInput" class="form-label"><b>Enter a dish name:</b></label>
217-
<input id="dishInput" class="form-control" @bind="dishInput" @bind:event="oninput" placeholder="e.g. Butter Chicken" />
192+
<label for="movieInput" class="form-label"><b>Enter a movie name:</b></label>
193+
<input id="movieInput" class="form-control" @bind="movieInput" @bind:event="oninput" placeholder="e.g. The Matrix" />
218194
</div>
219-
<button class="btn btn-primary mb-3" @onclick="GetRecipe" disabled="@IsGetRecipeDisabled">
195+
<button class="btn btn-primary mb-3" @onclick="GetMovieDetails" disabled="@IsGetMovieDisabled">
220196
@if (isLoading)
221197
{
222198
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
223199
<span class="visually-hidden">Loading...</span>
224200
}
225201
else
226202
{
227-
<span>Get Recipe</span>
203+
<span>Get Movie Details</span>
228204
}
229205
</button>
230206

231207
<div>
232-
<b>Structured Recipe:</b><br />
208+
<b>Structured Movie Details:</b><br />
233209
@if (isLoading)
234210
{
235211
<span>Loading...</span>
236212
}
237-
else if (recipe != null)
213+
else if (movieDetails != null)
238214
{
239215
<div class="card mt-2" style="background-color: #f8f9fa; border: 1px solid #e9ecef;">
240216
<div class="card-body">
241-
<h5 class="card-title">@recipe.Title</h5>
242-
<p class="card-text"><strong>Total time:</strong> @recipe.TotalTime</p>
243-
<p class="card-text"><strong>Origin:</strong> @recipe.CountryOfOrigin</p>
217+
<h5 class="card-title">@movieDetails.Title</h5>
218+
<p class="card-text"><strong>Year:</strong> @movieDetails.Year</p>
219+
<p class="card-text"><strong>Director:</strong> @movieDetails.Director</p>
220+
<p class="card-text"><strong>Description:</strong> @movieDetails.Description</p>
244221

245-
<h6>Ingredients</h6>
222+
<h6>Genres</h6>
246223
<ul>
247-
@foreach (var ing in recipe.Ingredients)
224+
@foreach (var genre in movieDetails.Genres)
248225
{
249-
<li>@ing</li>
226+
<li>@genre</li>
250227
}
251228
</ul>
252229

253-
<h6>Steps</h6>
254-
<ol>
255-
@foreach (var s in recipe.Steps)
230+
<h6>Actors</h6>
231+
<ul>
232+
@foreach (var actor in movieDetails.Actors)
256233
{
257-
<li>@s</li>
234+
<li>@actor</li>
258235
}
259-
</ol>
236+
</ul>
260237

261238
<details>
262239
<summary>Raw JSON</summary>
263-
<pre>@System.Text.Json.JsonSerializer.Serialize(recipe, new System.Text.Json.JsonSerializerOptions { WriteIndented = true })</pre>
240+
<pre>@System.Text.Json.JsonSerializer.Serialize(movieDetails, new System.Text.Json.JsonSerializerOptions { WriteIndented = true })</pre>
264241
</details>
265242
</div>
266243
</div>
267244
}
268245
else
269246
{
270-
<div class="alert alert-secondary mt-2">No recipe yetenter a dish and click <strong>Get Recipe</strong>.</div>
247+
<div class="alert alert-secondary mt-2">No movie details yetenter a movie name and click <strong>Get Movie Details</strong>.</div>
271248
}
272249
</div>
273250
</Section>

TestArena/Blog/Common/NavigationUtils/SiteMap.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ public static class SiteMap
143143
"/blog/ai/openai-rest-api/structured-output",
144144
new DateTime(2025, 8, 23),
145145
"images/blog/ai/openai-rest-api/structured-output/banner.png",
146-
["AI", "OpenAI", "API", "Structured Output", ".NET", "C#"]),
146+
["AI", "OpenAI", "API", "Structured Output", ".NET", "C#"], false),
147147
];
148148

149149
//"/blog/ai/openai-rest-api/structured-output"

TestArena/demo/OpenAIService.cs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public OpenAIService()
1919
/// <summary>
2020
/// Represents movie details created by the model.
2121
/// </summary>
22-
public record MovieDetails(string Title, int Year, string Director, string[] Genres, string[] Actors);
22+
public record MovieDetails(string Title, int Year, string Director, string Description, string[] Genres, string[] Actors);
2323

2424
/// <summary>
2525
/// Create movie details using a movie name. The model is instructed to return a
@@ -28,6 +28,7 @@ public record MovieDetails(string Title, int Year, string Director, string[] Gen
2828
/// - Title: max 100 chars
2929
/// - Year: valid year integer
3030
/// - Director: max 80 chars
31+
/// - Description: max 150 chars
3132
/// - Genres: up to 5 items, each max 30 chars
3233
/// - Actors: up to 10 items, each max 60 chars
3334
/// </summary>
@@ -39,10 +40,10 @@ public record MovieDetails(string Title, int Year, string Director, string[] Gen
3940
var url = "https://api.openai.com/v1/chat/completions";
4041

4142
var systemInstruction =
42-
"You are a JSON generator. Given a movie name, produce a single JSON object ONLY (no surrounding text, no markdown) with the exact shape:\n" +
43-
"{ \"title\": string, \"year\": number, \"director\": string, \"genres\": [string,...], \"actors\": [string,...] }\n" +
44-
"Constraints: title must be max 100 characters; year must be a valid year number; director must be max 80 characters; genres must be an array with at most 5 items; each genre max 30 characters; actors must be an array with at most 10 items; each actor max 60 characters.\n" +
45-
"If you cannot create values that meet these constraints, truncate fields to the required length. Respond only with a single JSON object and nothing else.";
43+
"You are a JSON generator. Given a movie name, produce a single JSON object ONLY (no surrounding text, no markdown) with the exact shape:\n" +
44+
"{ \"title\": string, \"year\": number, \"director\": string, \"description\": string, \"genres\": [string,...], \"actors\": [string,...] }\n" +
45+
"Constraints: title must be max 100 characters; year must be a valid year number; director must be max 80 characters; description must be max 150 characters; genres must be an array with at most 5 items; each genre max 30 characters; actors must be an array with at most 10 items; each actor max 60 characters.\n" +
46+
"If you cannot create values that meet these constraints, truncate fields to the required length. Respond only with a single JSON object and nothing else.";
4647

4748
var functions = new[]
4849
{
@@ -58,6 +59,7 @@ public record MovieDetails(string Title, int Year, string Director, string[] Gen
5859
title = new { type = "string", maxLength = 100, description = "Movie title (max 100 chars)" },
5960
year = new { type = "integer", description = "Release year" },
6061
director = new { type = "string", maxLength = 80, description = "Director name (max 80 chars)" },
62+
description = new { type = "string", maxLength = 150, description = "Movie description (max 150 chars)" },
6163
genres = new
6264
{
6365
type = "array",
@@ -71,7 +73,7 @@ public record MovieDetails(string Title, int Year, string Director, string[] Gen
7173
items = new { type = "string", maxLength = 60 }
7274
}
7375
},
74-
required = new[] { "title", "year", "director", "genres", "actors" }
76+
required = new[] { "title", "year", "director", "description", "genres", "actors" }
7577
}
7678
}
7779
};
@@ -145,6 +147,9 @@ public record MovieDetails(string Title, int Year, string Director, string[] Gen
145147
var director = root.TryGetProperty("director", out var dir) && dir.ValueKind == JsonValueKind.String
146148
? dir.GetString() ?? string.Empty
147149
: string.Empty;
150+
var description = root.TryGetProperty("description", out var desc) && desc.ValueKind == JsonValueKind.String
151+
? desc.GetString() ?? string.Empty
152+
: string.Empty;
148153

149154
var genres = new List<string>();
150155
if (root.TryGetProperty("genres", out var gen) && gen.ValueKind == JsonValueKind.Array)
@@ -179,6 +184,9 @@ public record MovieDetails(string Title, int Year, string Director, string[] Gen
179184
director = director.Trim();
180185
if (director.Length > 80) director = director.Substring(0, 80);
181186

187+
description = description.Trim();
188+
if (description.Length > 150) description = description.Substring(0, 150);
189+
182190
for (int i = 0; i < genres.Count; i++)
183191
{
184192
var genre = genres[i].Trim();
@@ -193,7 +201,7 @@ public record MovieDetails(string Title, int Year, string Director, string[] Gen
193201
actors[i] = actor;
194202
}
195203

196-
return new MovieDetails(title, year, director, genres.ToArray(), actors.ToArray());
204+
return new MovieDetails(title, year, director, description, genres.ToArray(), actors.ToArray());
197205
}
198206
catch (JsonException)
199207
{

TestArena/wwwroot/sitemap.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,9 @@
120120
<lastmod>2025-08-24</lastmod>
121121
<changefreq>monthly</changefreq>
122122
</url>
123+
<url>
124+
<loc>https://devcodex.in/blog/ai/openai-rest-api/structured-output</loc>
125+
<lastmod>2025-09-06</lastmod>
126+
<changefreq>monthly</changefreq>
127+
</url>
123128
</urlset>

0 commit comments

Comments
 (0)