Skip to content

Commit 26bac1a

Browse files
authored
Merge pull request #1367 from adenchen123/GTR-13072
Supports json response format for OpenAI
2 parents f8e6f38 + 34e9e88 commit 26bac1a

5 files changed

Lines changed: 57 additions & 13 deletions

File tree

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ public class AgentLlmConfig
44
{
55
public AgentLlmConfig() { }
66

7-
public AgentLlmConfig(AgentTemplateLlmConfig templateLlmConfig)
7+
public AgentLlmConfig(AgentTemplateConfig templateConfig)
88
{
9-
Provider = templateLlmConfig.Provider;
10-
Model = templateLlmConfig.Model;
11-
MaxOutputTokens = templateLlmConfig.MaxOutputTokens;
12-
ReasoningEffortLevel = templateLlmConfig.ReasoningEffortLevel;
9+
Provider = templateConfig.LlmConfig?.Provider;
10+
Model = templateConfig.LlmConfig?.Model;
11+
MaxOutputTokens = templateConfig.LlmConfig?.MaxOutputTokens;
12+
ReasoningEffortLevel = templateConfig.LlmConfig?.ReasoningEffortLevel;
13+
ResponseFormat = templateConfig.ResponseFormat;
1314
}
1415

1516
/// <summary>
@@ -72,6 +73,13 @@ public AgentLlmConfig(AgentTemplateLlmConfig templateLlmConfig)
7273
[JsonPropertyName("realtime")]
7374
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
7475
public LlmRealtimeConfig? Realtime { get; set; }
76+
77+
/// <summary>
78+
/// Response format: json, xml, markdown, yaml, etc.
79+
/// </summary>
80+
[JsonPropertyName("response_format")]
81+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
82+
public string? ResponseFormat { get; set; }
7583
}
7684

7785
public class LlmImageCompositionConfig : LlmProviderModel

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,10 +248,10 @@ private async Task<InstructResult> RunLlm(
248248
if (!string.IsNullOrEmpty(templateName))
249249
{
250250
prompt = agentService.RenderTemplate(agent, templateName);
251-
var templateLlmConfig = agent.Templates?.FirstOrDefault(x => x.Name.IsEqualTo(templateName))?.LlmConfig;
252-
if (templateLlmConfig?.IsValid == true)
251+
var template = agent.Templates?.FirstOrDefault(x => x.Name.IsEqualTo(templateName));
252+
if (template?.LlmConfig?.IsValid == true)
253253
{
254-
llmConfig = new AgentLlmConfig(templateLlmConfig);
254+
llmConfig = new AgentLlmConfig(template);
255255
}
256256
}
257257
else

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,9 @@ private async Task<Agent> BuildInnerAgent(InstructOptions? options)
6868
{
6969
var template = agent?.Templates?.FirstOrDefault(x => x.Name.IsEqualTo(options.TemplateName));
7070
instruction = BuildInstruction(template?.Content ?? string.Empty, options?.Data ?? []);
71-
var templateLlmConfig = template?.LlmConfig;
72-
if (templateLlmConfig?.IsValid == true)
71+
if (template?.LlmConfig?.IsValid == true)
7372
{
74-
llmConfig = new AgentLlmConfig(templateLlmConfig);
73+
llmConfig = new AgentLlmConfig(template);
7574
}
7675
}
7776
}

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -618,13 +618,30 @@ private ChatCompletionOptions InitChatCompletionOption(Agent agent)
618618
? tokens
619619
: agent.LlmConfig?.MaxOutputTokens ?? LlmConstant.DEFAULT_MAX_OUTPUT_TOKEN;
620620

621-
return new ChatCompletionOptions()
621+
var options = new ChatCompletionOptions()
622622
{
623623
Temperature = temperature,
624624
MaxOutputTokenCount = maxTokens,
625625
ReasoningEffortLevel = reasoningEffortLevel,
626626
WebSearchOptions = webSearchOptions
627627
};
628+
629+
if (webSearchOptions == null)
630+
{
631+
options.ResponseFormat = GetChatResponseFormat(agent.LlmConfig?.ResponseFormat);
632+
}
633+
634+
return options;
635+
}
636+
637+
private static ChatResponseFormat? GetChatResponseFormat(string? format)
638+
{
639+
return format?.ToLower() switch
640+
{
641+
"json" or "json_object" => ChatResponseFormat.CreateJsonObjectFormat(),
642+
"text" => ChatResponseFormat.CreateTextFormat(),
643+
_ => null
644+
};
628645
}
629646

630647
/// <summary>

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ private async Task<RoleDialogModel> InnerCreateResponseStreamingAsync(Agent agen
432432
var options = new CreateResponseOptions(_model, [])
433433
{
434434
Temperature = temperature,
435-
MaxOutputTokenCount = maxTokens
435+
MaxOutputTokenCount = maxTokens,
436436
};
437437

438438
var reasoningEffortLevel = ParseResponseReasoning(settings?.Reasoning, agent);
@@ -450,6 +450,16 @@ private async Task<RoleDialogModel> InnerCreateResponseStreamingAsync(Agent agen
450450
}
451451
}
452452

453+
// Response format
454+
var textFormat = GetResponseTextFormat(agent.LlmConfig?.ResponseFormat);
455+
if (textFormat != null)
456+
{
457+
options.TextOptions = new ResponseTextOptions
458+
{
459+
TextFormat = textFormat
460+
};
461+
}
462+
453463
// Prepare instruction and functions
454464
var renderData = agentService.CollectRenderData(agent);
455465
var (instruction, functions) = agentService.PrepareInstructionAndFunctions(agent, renderData);
@@ -580,6 +590,16 @@ private string GetResponseApiPrompt(CreateResponseOptions options)
580590
return sb.ToString();
581591
}
582592

593+
private static ResponseTextFormat? GetResponseTextFormat(string? format)
594+
{
595+
return format?.ToLower() switch
596+
{
597+
"json" or "json_object" => ResponseTextFormat.CreateJsonObjectFormat(),
598+
"text" => ResponseTextFormat.CreateTextFormat(),
599+
_ => null
600+
};
601+
}
602+
583603
private ResponseReasoningEffortLevel? ParseResponseReasoning(ReasoningSetting? settings, Agent agent)
584604
{
585605
ResponseReasoningEffortLevel? reasoningEffortLevel = null;

0 commit comments

Comments
 (0)