Skip to content
Merged

revert #1424

Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,19 @@ private async Task<RoleDialogModel> InnerCreateResponse(Agent agent, List<RoleDi
var response = await responsesClient.CreateResponseAsync(options);
var value = response.Value;

var functionCall = value.OutputItems.OfType<FunctionCallResponseItem>().FirstOrDefault();
// Every call the model asked for, not only the first. It routinely asks for several
// independent ones at once, and keeping one made it re-ask for the rest next turn.
var functionCalls = value.OutputItems.OfType<FunctionCallResponseItem>().ToList();
var toolCalls = ToLlmToolCalls(functionCalls);
var functionCall = functionCalls.FirstOrDefault();
var reasoningItem = value.OutputItems.OfType<ReasoningResponseItem>().FirstOrDefault();
var text = value.GetOutputText() ?? string.Empty;
var thinkingText = reasoningItem?.GetSummaryText();

RoleDialogModel responseMessage;
if (functionCall != null)
{
_logger.LogInformation($"Action: {nameof(InnerCreateResponse)}, Agent: {agent.Name}, ToolCall: {functionCall.FunctionName}");
_logger.LogInformation($"Action: {nameof(InnerCreateResponse)}, Agent: {agent.Name}, ToolCalls: {string.Join(",", toolCalls.Select(x => x.FunctionName))}");

responseMessage = new RoleDialogModel(AgentRole.Function, text)
{
Expand All @@ -42,6 +46,7 @@ private async Task<RoleDialogModel> InnerCreateResponse(Agent agent, List<RoleDi
ToolCallId = functionCall.CallId,
FunctionName = functionCall.FunctionName.NormalizeFunctionName(),
FunctionArgs = functionCall.FunctionArguments?.ToString(),
ToolCalls = toolCalls,

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. Additional tool calls are dropped 🐞 Bug ≡ Correctness

The provider now returns every requested call in ToolCalls, but RoutingService.InvokeAgent
copies and executes only the legacy singular fields. Any second or later tool call is discarded and
omitted from subsequent Responses API history, so the model can re-request it on the next turn.
Agent Prompt
## Issue description
OpenAI Responses now returns all model-requested calls through `RoleDialogModel.ToolCalls`, but the main routing path executes only the first call. Update orchestration to execute every call, retain each call ID and result, and send all call/result pairs back before requesting the next completion.

## Issue Context
The provider keeps the first call in legacy singular fields for compatibility. Multi-call orchestration must avoid recursively requesting another completion after each individual call; all calls from one model response need corresponding outputs in the next request.

## Fix Focus Areas
- src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.Response.cs[28-49]
- src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs[39-65]
- src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs[89-130]
- src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.Response.cs[519-531]

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

RenderedInstruction = string.Join("\r\n", renderedInstructions)
};
}
Expand Down Expand Up @@ -113,15 +118,19 @@ private async Task<bool> InnerCreateResponseAsync(Agent agent,
var response = await responsesClient.CreateResponseAsync(options);
var value = response.Value;

var functionCall = value.OutputItems.OfType<FunctionCallResponseItem>().FirstOrDefault();
// Every call the model asked for, not only the first. It routinely asks for several
// independent ones at once, and keeping one made it re-ask for the rest next turn.
var functionCalls = value.OutputItems.OfType<FunctionCallResponseItem>().ToList();
var toolCalls = ToLlmToolCalls(functionCalls);
var functionCall = functionCalls.FirstOrDefault();
var reasoningItem = value.OutputItems.OfType<ReasoningResponseItem>().FirstOrDefault();
var text = value.GetOutputText() ?? string.Empty;
var thinkingText = reasoningItem?.GetSummaryText();

RoleDialogModel responseMessage;
if (functionCall != null)
{
_logger.LogInformation($"Action: {nameof(InnerCreateResponseAsync)}, Agent: {agent.Name}, ToolCall: {functionCall.FunctionName}");
_logger.LogInformation($"Action: {nameof(InnerCreateResponseAsync)}, Agent: {agent.Name}, ToolCalls: {string.Join(",", toolCalls.Select(x => x.FunctionName))}");

responseMessage = new RoleDialogModel(AgentRole.Function, text)
{
Expand All @@ -130,6 +139,7 @@ private async Task<bool> InnerCreateResponseAsync(Agent agent,
ToolCallId = functionCall.CallId,
FunctionName = functionCall.FunctionName.NormalizeFunctionName(),
FunctionArgs = functionCall.FunctionArguments?.ToString(),
ToolCalls = toolCalls,
RenderedInstruction = string.Join("\r\n", renderedInstructions)
};
}
Expand Down Expand Up @@ -221,7 +231,7 @@ private async Task<RoleDialogModel> InnerCreateResponseStreamingAsync(Agent agen

using var textStream = new RealtimeTextStream();
using var thinkingStream = new RealtimeTextStream();
FunctionCallResponseItem? functionCall = null;
var functionCalls = new List<FunctionCallResponseItem>();
ResponseResult? finalResult = null;
ResponseTokenUsage? tokenUsage = null;

Expand Down Expand Up @@ -312,7 +322,7 @@ private async Task<RoleDialogModel> InnerCreateResponseStreamingAsync(Agent agen
{
if (itemDone.Item is FunctionCallResponseItem fc)
{
functionCall = fc;
functionCalls.Add(fc);
#if DEBUG
_logger.LogDebug($"Tool Call (id: {fc.CallId}) => {fc.FunctionName}({fc.FunctionArguments})");
#endif
Expand Down Expand Up @@ -342,9 +352,12 @@ private async Task<RoleDialogModel> InnerCreateResponseStreamingAsync(Agent agen
var allText = textStream.GetText();
var thinkingText = thinkingStream.GetText();

var toolCalls = ToLlmToolCalls(functionCalls);
var functionCall = functionCalls.FirstOrDefault();

if (functionCall != null)
{
_logger.LogInformation($"Action: {nameof(InnerCreateResponseStreamingAsync)}, Agent: {agent.Name}, ToolCall: {functionCall.FunctionName}");
_logger.LogInformation($"Action: {nameof(InnerCreateResponseStreamingAsync)}, Agent: {agent.Name}, ToolCalls: {string.Join(",", toolCalls.Select(x => x.FunctionName))}");

responseMessage = new RoleDialogModel(AgentRole.Function, allText)
{
Expand All @@ -353,6 +366,7 @@ private async Task<RoleDialogModel> InnerCreateResponseStreamingAsync(Agent agen
ToolCallId = functionCall.CallId,
FunctionName = functionCall.FunctionName.NormalizeFunctionName(),
FunctionArgs = functionCall.FunctionArguments?.ToString(),
ToolCalls = toolCalls,
RenderedInstruction = string.Join("\r\n", renderedInstructions)
};
}
Expand Down Expand Up @@ -424,14 +438,12 @@ private async Task<RoleDialogModel> InnerCreateResponseStreamingAsync(Agent agen
var allowMultiModal = settings != null && settings.MultiModal;
renderedInstructions = [];

float? temperature = float.Parse(_state.GetState("temperature", "0.0"));
var maxTokens = int.TryParse(_state.GetState("max_tokens"), out var tokens)
? tokens
: agent.LlmConfig?.MaxOutputTokens ?? LlmConstant.DEFAULT_MAX_OUTPUT_TOKEN;

var options = new CreateResponseOptions(_model, [])
{
Temperature = temperature,

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. Responses temperature is ignored 🐞 Bug ≡ Correctness

Removing CreateResponseOptions.Temperature causes every Responses API request to ignore the
conversation's temperature state and use the SDK/API default instead. Configured deterministic or
creative behavior therefore changes whenever UseResponseApi is enabled, while the Chat Completions
path still honors the same state.
Agent Prompt
## Issue description
Restore propagation of the configured conversation temperature into `CreateResponseOptions`. Continue suppressing temperature only for Responses configurations that cannot accept it, such as the reasoning modes previously covered by the conditional.

## Issue Context
`PrepareResponseOptions` is shared by synchronous, callback-based, and streaming Responses API operations. The sibling Chat Completions initializer demonstrates that `temperature` is conversation state intended to affect provider requests.

## Fix Focus Areas
- src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.Response.cs[433-459]
- src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.Chat.cs[607-639]

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

MaxOutputTokenCount = maxTokens,
};

Expand All @@ -444,11 +456,6 @@ private async Task<RoleDialogModel> InnerCreateResponseStreamingAsync(Agent agen
ReasoningEffortLevel = reasoningEffortLevel.Value,
ReasoningSummaryVerbosity = ResponseReasoningSummaryVerbosity.Auto
};

if (reasoningEffortLevel != ResponseReasoningEffortLevel.None)
{
options.Temperature = null;
}
}

// Response format
Expand Down Expand Up @@ -822,4 +829,9 @@ private void SetResponseFormat(CreateResponseOptions options, AgentLlmConfig? ll
} : null;
}
#endregion

private static List<LlmToolCall> ToLlmToolCalls(IEnumerable<FunctionCallResponseItem>? functionCalls)
=> (functionCalls ?? [])
.Select(x => new LlmToolCall(x.CallId, x.FunctionName, x.FunctionArguments?.ToString()))
.ToList();
}
Loading