Skip to content

Supports json response format for OpenAI#1367

Merged
iceljc merged 2 commits into
SciSharp:masterfrom
adenchen123:GTR-13072
Jun 24, 2026
Merged

Supports json response format for OpenAI#1367
iceljc merged 2 commits into
SciSharp:masterfrom
adenchen123:GTR-13072

Conversation

@adenchen123

Copy link
Copy Markdown
Contributor

No description provided.

@qodo-code-review

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Add configurable JSON response format support for OpenAI
✨ Enhancement 🕐 20-40 Minutes

Grey Divider

Description

• Add response_format to agent LLM config and template-derived config.
• Propagate template response format into OpenAI Chat/Response option builders.
• Map "json"/"text" strings to OpenAI SDK response format objects.
Diagram

flowchart TD
  A["Template config"] --> B["LLM config"] --> C["Instruct service"] --> D["OpenAI provider"] --> E{"Web search?"}
  E -->|"No"| F["Set format"] --> G["OpenAI API"]
  E -->|"Yes"| H["Omit format"] --> G["OpenAI API"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Strongly-typed ResponseFormat enum
  • ➕ Prevents typos and unknown values (compile-time safety where used).
  • ➕ Centralizes supported formats and makes validation explicit.
  • ➕ Easier to document and discover via intellisense.
  • ➖ Harder to extend with provider-specific or future formats without redeploying.
  • ➖ May require additional mapping layers for different provider SDK types.
2. Provider-specific response format config block
  • ➕ Avoids leaking provider constraints into a generic string field.
  • ➕ Can model richer options (e.g., strict JSON schema settings) per provider.
  • ➖ More configuration surface area and complexity for users.
  • ➖ Requires more plumbing through agent/template config layers.

Recommendation: The current string-based approach is a pragmatic minimal change and fits existing config patterns. Consider adding lightweight validation (or an optional enum wrapper) at config-load time to catch unsupported values early, while still allowing future provider-specific expansion.

Files changed (5) +57 / -13

Enhancement (5) +57 / -13
AgentLlmConfig.csAdd ResponseFormat to AgentLlmConfig and derive it from templates +13/-5

Add ResponseFormat to AgentLlmConfig and derive it from templates

• Extends AgentLlmConfig with a nullable response_format field. Updates the template-based constructor to take AgentTemplateConfig and copy both LLM settings and ResponseFormat from the template.

src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentLlmConfig.cs

InstructService.Execute.csBuild AgentLlmConfig from the full template (not just LlmConfig) +3/-3

Build AgentLlmConfig from the full template (not just LlmConfig)

• Updates template selection to pass the full template into AgentLlmConfig construction. Enables non-LLM template settings (e.g., response format) to flow into runtime config.

src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs

InstructService.Instruct.csPropagate template response format into inner agent LLM config +2/-3

Propagate template response format into inner agent LLM config

• Adjusts inner agent building to construct AgentLlmConfig from the template object when the template LLM config is valid. This ensures response format is included when using templated instructions.

src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Instruct.cs

ChatCompletionProvider.Chat.csSet ChatCompletionOptions.ResponseFormat from agent config +18/-1

Set ChatCompletionOptions.ResponseFormat from agent config

• Constructs ChatCompletionOptions into a variable and conditionally applies ResponseFormat when web search is not enabled. Adds a mapping helper supporting "json"/"json_object" and "text" formats.

src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.Chat.cs

ChatCompletionProvider.Response.csApply response text format to response-streaming options +21/-1

Apply response text format to response-streaming options

• Adds ResponseTextOptions.TextFormat based on agent ResponseFormat for the responses API streaming path. Introduces a mapping helper consistent with the chat path ("json"/"json_object" and "text").

src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.Response.cs

@qodo-code-review

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (1) 📜 Skill insights (0)

Grey Divider


Action required

1. GetChatResponseFormat uses ToLower() 📘 Rule violation ≡ Correctness
Description
The new response-format parsing normalizes with format?.ToLower(), which is culture-sensitive and
can cause incorrect identifier matching in some locales. This violates the requirement to use
ordinal, case-insensitive comparisons for identifier-like strings.
Code

src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.Chat.cs[R639-644]

+        return format?.ToLower() switch
+        {
+            "json" or "json_object" => ChatResponseFormat.CreateJsonObjectFormat(),
+            "text" => ChatResponseFormat.CreateTextFormat(),
+            _ => null
+        };
Evidence
Rule 2 requires ordinal, case-insensitive comparisons for identifier-like strings, but the added
parsing logic lowercases agent.LlmConfig?.ResponseFormat via format?.ToLower() (a
culture-sensitive operation) before matching identifier values like json and text, creating
locale-dependent behavior that can mis-match these identifiers.

src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.Chat.cs[637-644]
src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.Response.cs[593-601]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The response-format parsing currently normalizes `agent.LlmConfig?.ResponseFormat` using `format?.ToLower()` before switching/matching, which performs a culture-sensitive conversion and can lead to incorrect identifier matching in certain locales. Update this logic to use ordinal, case-insensitive comparisons suitable for identifier-like strings.

## Issue Context
This parsing logic interprets `agent.LlmConfig?.ResponseFormat` values such as `json` and `text`.

## Fix Focus Areas
- src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.Chat.cs[637-644]
- src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.Response.cs[593-601]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Template drops response_format 🐞 Bug ≡ Correctness
Description
When a template has a valid LlmConfig, InstructService replaces the agent’s LlmConfig with
new AgentLlmConfig(template), and the new constructor sets ResponseFormat only from
template.ResponseFormat. If the template omits response_format but the agent-level
LlmConfig.ResponseFormat is set (e.g., "json"), OpenAI requests won’t enable JSON mode for
template-based executions.
Code

src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentLlmConfig.cs[R7-13]

+    public AgentLlmConfig(AgentTemplateConfig templateConfig)
    {
-        Provider = templateLlmConfig.Provider;
-        Model = templateLlmConfig.Model;
-        MaxOutputTokens = templateLlmConfig.MaxOutputTokens;
-        ReasoningEffortLevel = templateLlmConfig.ReasoningEffortLevel;
+        Provider = templateConfig.LlmConfig?.Provider;
+        Model = templateConfig.LlmConfig?.Model;
+        MaxOutputTokens = templateConfig.LlmConfig?.MaxOutputTokens;
+        ReasoningEffortLevel = templateConfig.LlmConfig?.ReasoningEffortLevel;
+        ResponseFormat = templateConfig.ResponseFormat;
Evidence
InstructService swaps to a new template-derived AgentLlmConfig, and the OpenAI provider uses
agent.LlmConfig.ResponseFormat to decide whether to set response format; if the new config has
ResponseFormat == null, JSON mode won’t be enabled even if the original agent config had it set.

src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs[246-256]
src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentLlmConfig.cs[7-14]
src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs[353-369]
src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.Chat.cs[621-645]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Template-based LLM overrides currently replace the entire `AgentLlmConfig` and copy `ResponseFormat` only from the template. This causes agent-level `LlmConfig.ResponseFormat` (e.g., `json`) to be lost whenever the template has `LlmConfig.IsValid == true` but does not explicitly set `ResponseFormat`.

## Issue Context
- `InstructService.Execute` and `BuildInnerAgent` create a fresh `AgentLlmConfig` from the template when template `LlmConfig` is valid.
- OpenAI providers read `agent.LlmConfig.ResponseFormat` to set `ChatCompletionOptions.ResponseFormat` / `CreateResponseOptions.TextOptions.TextFormat`.

## Fix Focus Areas
- src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentLlmConfig.cs[7-14]
- src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs[246-256]
- src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Instruct.cs[67-75]

## Implementation direction
Ensure `ResponseFormat` (and ideally other non-overridden LLM settings) fall back to the agent-level config when the template does not specify them. For example:
- Build `llmConfig` by cloning/starting from `agent.LlmConfig`, then override only the fields present in `template.LlmConfig` and `template.ResponseFormat`.
- Or update the constructor usage so that `ResponseFormat = template.ResponseFormat ?? agent.LlmConfig?.ResponseFormat` (requires passing the base config or doing the merge at the call site).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

3. ResponseFormat not normalized 🐞 Bug ☼ Reliability
Description
GetChatResponseFormat / GetResponseTextFormat do not trim input before matching, so config
values like "json " won’t be recognized and will silently disable response formatting. This makes
JSON mode brittle to minor config formatting issues.
Code

src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.Chat.cs[R637-644]

+    private static ChatResponseFormat? GetChatResponseFormat(string? format)
+    {
+        return format?.ToLower() switch
+        {
+            "json" or "json_object" => ChatResponseFormat.CreateJsonObjectFormat(),
+            "text" => ChatResponseFormat.CreateTextFormat(),
+            _ => null
+        };
Evidence
Both helpers switch on format?.ToLower() without trimming; any whitespace in config values
prevents matches and results in null format options.

src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.Chat.cs[637-645]
src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.Response.cs[593-601]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Response format parsing is strict on exact tokens after `ToLower()`, so trailing/leading whitespace (e.g., `"json "`) results in `null` and disables response formatting.

## Issue Context
`ResponseFormat` is user/config-provided (agent JSON / template-configs.json), so it can reasonably include whitespace.

## Fix Focus Areas
- src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.Chat.cs[637-645]
- src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.Response.cs[593-601]

## Implementation direction
Normalize with something like:
```csharp
var normalized = format?.Trim();
return normalized?.Equals("json", StringComparison.OrdinalIgnoreCase) == true ...
```
(or `Trim().ToLowerInvariant()` before the switch).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment on lines +639 to +644
return format?.ToLower() switch
{
"json" or "json_object" => ChatResponseFormat.CreateJsonObjectFormat(),
"text" => ChatResponseFormat.CreateTextFormat(),
_ => null
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. getchatresponseformat uses tolower() 📘 Rule violation ≡ Correctness

The new response-format parsing normalizes with format?.ToLower(), which is culture-sensitive and
can cause incorrect identifier matching in some locales. This violates the requirement to use
ordinal, case-insensitive comparisons for identifier-like strings.
Agent Prompt
## Issue description
The response-format parsing currently normalizes `agent.LlmConfig?.ResponseFormat` using `format?.ToLower()` before switching/matching, which performs a culture-sensitive conversion and can lead to incorrect identifier matching in certain locales. Update this logic to use ordinal, case-insensitive comparisons suitable for identifier-like strings.

## Issue Context
This parsing logic interprets `agent.LlmConfig?.ResponseFormat` values such as `json` and `text`.

## Fix Focus Areas
- src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.Chat.cs[637-644]
- src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.Response.cs[593-601]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +7 to +13
public AgentLlmConfig(AgentTemplateConfig templateConfig)
{
Provider = templateLlmConfig.Provider;
Model = templateLlmConfig.Model;
MaxOutputTokens = templateLlmConfig.MaxOutputTokens;
ReasoningEffortLevel = templateLlmConfig.ReasoningEffortLevel;
Provider = templateConfig.LlmConfig?.Provider;
Model = templateConfig.LlmConfig?.Model;
MaxOutputTokens = templateConfig.LlmConfig?.MaxOutputTokens;
ReasoningEffortLevel = templateConfig.LlmConfig?.ReasoningEffortLevel;
ResponseFormat = templateConfig.ResponseFormat;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Template drops response_format 🐞 Bug ≡ Correctness

When a template has a valid LlmConfig, InstructService replaces the agent’s LlmConfig with
new AgentLlmConfig(template), and the new constructor sets ResponseFormat only from
template.ResponseFormat. If the template omits response_format but the agent-level
LlmConfig.ResponseFormat is set (e.g., "json"), OpenAI requests won’t enable JSON mode for
template-based executions.
Agent Prompt
## Issue description
Template-based LLM overrides currently replace the entire `AgentLlmConfig` and copy `ResponseFormat` only from the template. This causes agent-level `LlmConfig.ResponseFormat` (e.g., `json`) to be lost whenever the template has `LlmConfig.IsValid == true` but does not explicitly set `ResponseFormat`.

## Issue Context
- `InstructService.Execute` and `BuildInnerAgent` create a fresh `AgentLlmConfig` from the template when template `LlmConfig` is valid.
- OpenAI providers read `agent.LlmConfig.ResponseFormat` to set `ChatCompletionOptions.ResponseFormat` / `CreateResponseOptions.TextOptions.TextFormat`.

## Fix Focus Areas
- src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentLlmConfig.cs[7-14]
- src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs[246-256]
- src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Instruct.cs[67-75]

## Implementation direction
Ensure `ResponseFormat` (and ideally other non-overridden LLM settings) fall back to the agent-level config when the template does not specify them. For example:
- Build `llmConfig` by cloning/starting from `agent.LlmConfig`, then override only the fields present in `template.LlmConfig` and `template.ResponseFormat`.
- Or update the constructor usage so that `ResponseFormat = template.ResponseFormat ?? agent.LlmConfig?.ResponseFormat` (requires passing the base config or doing the merge at the call site).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@yileicn yileicn requested a review from iceljc June 24, 2026 03:22
@iceljc iceljc merged commit 26bac1a into SciSharp:master Jun 24, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants