-
Notifications
You must be signed in to change notification settings - Fork 186
Add demo for structured outputs #375
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,242 @@ | ||||||||||||||||||||
| using System.Text.Json; | ||||||||||||||||||||
| using System.Text.Json.Schema; | ||||||||||||||||||||
| using System.Text.Json.Serialization; | ||||||||||||||||||||
| using OllamaSharp; | ||||||||||||||||||||
| using OllamaSharp.Models.Chat; | ||||||||||||||||||||
| using Spectre.Console; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| namespace OllamaApiConsole.Demos; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||
| /// Demonstrates structured outputs by extracting a recipe in a strongly-typed JSON schema. | ||||||||||||||||||||
| /// The user types a dish name and the model returns structured data that is rendered as a formatted table. | ||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||
| public class StructuredOutputConsole(IOllamaApiClient ollama) : OllamaConsole(ollama) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| private static readonly JsonSerializerOptions SERIALIZER_OPTIONS = new() | ||||||||||||||||||||
| { | ||||||||||||||||||||
| PropertyNameCaseInsensitive = true, | ||||||||||||||||||||
| DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull | ||||||||||||||||||||
| }; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /// <inheritdoc/> | ||||||||||||||||||||
| public override async Task Run() | ||||||||||||||||||||
| { | ||||||||||||||||||||
| AnsiConsole.Write(new Rule("Structured outputs").LeftJustified()); | ||||||||||||||||||||
| AnsiConsole.WriteLine(); | ||||||||||||||||||||
| AnsiConsole.MarkupLine("This demo asks the model to return data that exactly matches a predefined JSON schema."); | ||||||||||||||||||||
| AnsiConsole.MarkupLine($"Type the name of any dish and get back a structured [{AccentTextColor}]recipe[/] — no free-form text, only typed data."); | ||||||||||||||||||||
| AnsiConsole.WriteLine(); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| Ollama.SelectedModel = await SelectModel("Select a model you want to use:"); | ||||||||||||||||||||
| SetThink(new ThinkValue(false)); | ||||||||||||||||||||
|
||||||||||||||||||||
| SetThink(new ThinkValue(false)); | |
| SetThink(false); |
Copilot
AI
Mar 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SetThink(new ThinkValue(false)) is called before the guard that checks whether a model was selected (line 34). If the user presses "back" at the model selection prompt, Ollama.SelectedModel will be empty and the demo exits immediately, but "Think mode is false." will still be printed to the console. This produces a confusing message when the user never actually entered the demo. The SetThink call should be moved to after the empty-model guard, consistent with the flow in other demos that do not emit side-effect output before checking a precondition.
| SetThink(new ThinkValue(false)); | |
| if (string.IsNullOrEmpty(Ollama.SelectedModel)) | |
| return; | |
| if (string.IsNullOrEmpty(Ollama.SelectedModel)) | |
| return; | |
| SetThink(new ThinkValue(false)); |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -144,7 +144,15 @@ protected void WriteChatInstructionHint() | |||||
| internal void ToggleThink() | ||||||
| { | ||||||
| // null -> false -> true -> null -> ... | ||||||
| Think = Think == null ? false : ((bool?)Think == false ? true : ((bool?)Think == true ? null : false)); | ||||||
| SetThink(Think == null ? false : ((bool?)Think == false ? true : ((bool?)Think == true ? null : false))); | ||||||
| } | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// Toggles the think mode between null, false, and true. | ||||||
|
||||||
| /// Toggles the think mode between null, false, and true. | |
| /// Sets the think mode to the specified value and prints the updated state to the console. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The field
SERIALIZER_OPTIONSusesUPPER_CASEnaming, which in this codebase is reserved forconstfields (e.g.,MULTILINE_OPEN,MULTILINE_CLOSE,START_NEW_COMMAND). This is aprivate static readonlyfield, not a constant. It should follow the naming convention for non-const fields. Thesrcproject uses_camelCasefor private readonly statics (e.g.,_schemaTransformCacheinAbstractionMapper.cs). Renaming to_serializerOptionswould align with the established convention.