mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-09-27 03:33:37 +00:00
Fixing some things left over
This commit is contained in:
parent
bbbcdddc91
commit
b28f2bcf6c
@ -1,3 +1,5 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
using AIStudio.Tools.PluginSystem;
|
using AIStudio.Tools.PluginSystem;
|
||||||
|
|
||||||
namespace AIStudio.Provider;
|
namespace AIStudio.Provider;
|
||||||
@ -20,6 +22,12 @@ public readonly record struct Model(string Id, string? DisplayName)
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly Model SYSTEM_MODEL = new(SYSTEM_MODEL_ID, null);
|
public static readonly Model SYSTEM_MODEL = new(SYSTEM_MODEL_ID, null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The provider-reported default reasoning behavior, when the model catalog supplies it.
|
||||||
|
/// </summary>
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
|
||||||
|
public ModelReasoningBehavior ReasoningBehavior { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if this model is the system-configured placeholder.
|
/// Checks if this model is the system-configured placeholder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -71,4 +79,15 @@ public readonly record struct Model(string Id, string? DisplayName)
|
|||||||
public override int GetHashCode() => this.Id?.GetHashCode(StringComparison.Ordinal) ?? 0;
|
public override int GetHashCode() => this.Id?.GetHashCode(StringComparison.Ordinal) ?? 0;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Describes the default reasoning behavior reported by a provider's model catalog.
|
||||||
|
/// </summary>
|
||||||
|
public enum ModelReasoningBehavior
|
||||||
|
{
|
||||||
|
UNKNOWN,
|
||||||
|
OPTIONAL,
|
||||||
|
DEFAULT_ON,
|
||||||
|
ALWAYS_ON,
|
||||||
|
}
|
||||||
|
|||||||
@ -15,6 +15,8 @@ public sealed class ResponsesToolCallingAdapter(Model chatModel, IList<object> b
|
|||||||
IReadOnlyList<(ToolDefinition Definition, IToolImplementation Implementation)> runnableTools,
|
IReadOnlyList<(ToolDefinition Definition, IToolImplementation Implementation)> runnableTools,
|
||||||
Func<ResponsesAPIRequest, CancellationToken, Task<ResponsesResponse?>> executeRequestAsync) : IToolCallingProviderAdapter
|
Func<ResponsesAPIRequest, CancellationToken, Task<ResponsesResponse?>> executeRequestAsync) : IToolCallingProviderAdapter
|
||||||
{
|
{
|
||||||
|
private const string ENCRYPTED_REASONING_INCLUDE = "reasoning.encrypted_content";
|
||||||
|
|
||||||
private readonly List<object> internalItems = [];
|
private readonly List<object> internalItems = [];
|
||||||
private ResponsesResponse? lastResponse;
|
private ResponsesResponse? lastResponse;
|
||||||
|
|
||||||
@ -48,7 +50,7 @@ public sealed class ResponsesToolCallingAdapter(Model chatModel, IList<object> b
|
|||||||
Stream = false,
|
Stream = false,
|
||||||
Store = false,
|
Store = false,
|
||||||
Tools = includeTools ? this.effectiveProviderTools : [],
|
Tools = includeTools ? this.effectiveProviderTools : [],
|
||||||
AdditionalApiParameters = apiParameters,
|
AdditionalApiParameters = IncludeEncryptedReasoning(apiParameters),
|
||||||
}, token);
|
}, token);
|
||||||
|
|
||||||
if (response is null)
|
if (response is null)
|
||||||
@ -92,6 +94,37 @@ public sealed class ResponsesToolCallingAdapter(Model chatModel, IList<object> b
|
|||||||
Output = content,
|
Output = content,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Request encrypted reasoning content without replacing any additional output data selected by the user.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Tool rounds use stateless Responses requests. OpenAI requires encrypted reasoning items in that mode
|
||||||
|
/// so that the complete output can be passed back with the tool result on the next round.
|
||||||
|
/// </remarks>
|
||||||
|
private static IDictionary<string, object> IncludeEncryptedReasoning(IDictionary<string, object> apiParameters)
|
||||||
|
{
|
||||||
|
var result = new Dictionary<string, object>(apiParameters);
|
||||||
|
var includeKey = result.Keys.FirstOrDefault(key => key.Equals("include", StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (includeKey is null)
|
||||||
|
{
|
||||||
|
result["include"] = new List<object> { ENCRYPTED_REASONING_INCLUDE };
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
var includedOutput = result[includeKey] switch
|
||||||
|
{
|
||||||
|
IEnumerable<object> values => values.ToList(),
|
||||||
|
string value => new List<object> { value },
|
||||||
|
_ => [],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!includedOutput.Any(value => string.Equals(value as string, ENCRYPTED_REASONING_INCLUDE, StringComparison.Ordinal)))
|
||||||
|
includedOutput.Add(ENCRYPTED_REASONING_INCLUDE);
|
||||||
|
|
||||||
|
result[includeKey] = includedOutput;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private static IList<object> BuildEffectiveProviderTools(IList<object> providerTools, IReadOnlyList<(ToolDefinition Definition, IToolImplementation Implementation)> runnableTools)
|
private static IList<object> BuildEffectiveProviderTools(IList<object> providerTools, IReadOnlyList<(ToolDefinition Definition, IToolImplementation Implementation)> runnableTools)
|
||||||
{
|
{
|
||||||
var localFunctionNames = runnableTools
|
var localFunctionNames = runnableTools
|
||||||
|
|||||||
@ -9,10 +9,10 @@ internal static class ThinkingContent
|
|||||||
{
|
{
|
||||||
public static string Get(string? reasoningContent, string? reasoning, IEnumerable<JsonElement>? reasoningDetails)
|
public static string Get(string? reasoningContent, string? reasoning, IEnumerable<JsonElement>? reasoningDetails)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(reasoningContent))
|
if (!string.IsNullOrEmpty(reasoningContent))
|
||||||
return reasoningContent;
|
return reasoningContent;
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(reasoning))
|
if (!string.IsNullOrEmpty(reasoning))
|
||||||
return reasoning;
|
return reasoning;
|
||||||
|
|
||||||
if (reasoningDetails is null)
|
if (reasoningDetails is null)
|
||||||
|
|||||||
@ -5,4 +5,24 @@ namespace AIStudio.Provider.OpenRouter;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="Id">The model's ID.</param>
|
/// <param name="Id">The model's ID.</param>
|
||||||
/// <param name="Name">The model's human-readable display name.</param>
|
/// <param name="Name">The model's human-readable display name.</param>
|
||||||
public readonly record struct OpenRouterModel(string Id, string? Name);
|
/// <param name="Reasoning">The model's provider-reported reasoning behavior.</param>
|
||||||
|
public readonly record struct OpenRouterModel(string Id, string? Name, OpenRouterReasoning? Reasoning)
|
||||||
|
{
|
||||||
|
public Model ToModel() => new(this.Id, this.Name)
|
||||||
|
{
|
||||||
|
ReasoningBehavior = this.Reasoning switch
|
||||||
|
{
|
||||||
|
{ Mandatory: true } => ModelReasoningBehavior.ALWAYS_ON,
|
||||||
|
{ DefaultEnabled: true } => ModelReasoningBehavior.DEFAULT_ON,
|
||||||
|
not null => ModelReasoningBehavior.OPTIONAL,
|
||||||
|
_ => ModelReasoningBehavior.UNKNOWN,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The reasoning defaults returned for a model by OpenRouter's model catalog.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="DefaultEnabled">Whether reasoning is enabled when the request does not configure it.</param>
|
||||||
|
/// <param name="Mandatory">Whether reasoning cannot be disabled for the model.</param>
|
||||||
|
public readonly record struct OpenRouterReasoning(bool DefaultEnabled, bool Mandatory);
|
||||||
|
|||||||
@ -116,7 +116,7 @@ public sealed class ProviderOpenRouter() : BaseProvider(LLMProviders.OPEN_ROUTER
|
|||||||
storeType,
|
storeType,
|
||||||
"models",
|
"models",
|
||||||
modelResponse => modelResponse.Data
|
modelResponse => modelResponse.Data
|
||||||
.Select(n => new Model(n.Id, n.Name))
|
.Select(n => n.ToModel())
|
||||||
.Where(model => model.IsChatModel()),
|
.Where(model => model.IsChatModel()),
|
||||||
apiKeyProvisional,
|
apiKeyProvisional,
|
||||||
requestConfigurator: (request, secretKey) =>
|
requestConfigurator: (request, secretKey) =>
|
||||||
@ -133,7 +133,7 @@ public sealed class ProviderOpenRouter() : BaseProvider(LLMProviders.OPEN_ROUTER
|
|||||||
return this.LoadModelsResponse<OpenRouterModelsResponse>(
|
return this.LoadModelsResponse<OpenRouterModelsResponse>(
|
||||||
SecretStoreType.EMBEDDING_PROVIDER,
|
SecretStoreType.EMBEDDING_PROVIDER,
|
||||||
"embeddings/models",
|
"embeddings/models",
|
||||||
modelResponse => modelResponse.Data.Select(n => new Model(n.Id, n.Name)),
|
modelResponse => modelResponse.Data.Select(n => n.ToModel()),
|
||||||
apiKeyProvisional,
|
apiKeyProvisional,
|
||||||
requestConfigurator: (request, secretKey) =>
|
requestConfigurator: (request, secretKey) =>
|
||||||
{
|
{
|
||||||
|
|||||||
@ -57,7 +57,29 @@ public static partial class ProviderExtensions
|
|||||||
_ => GetModelCapabilitiesOpenSource(bareModel),
|
_ => GetModelCapabilitiesOpenSource(bareModel),
|
||||||
};
|
};
|
||||||
|
|
||||||
return NormalizeForGateway(capabilities);
|
return ApplyGatewayReasoningBehavior(NormalizeForGateway(capabilities), model.ReasoningBehavior);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies provider-reported reasoning behavior in preference to model-name heuristics.
|
||||||
|
/// </summary>
|
||||||
|
private static List<Capability> ApplyGatewayReasoningBehavior(List<Capability> capabilities, ModelReasoningBehavior reasoningBehavior)
|
||||||
|
{
|
||||||
|
if (reasoningBehavior is ModelReasoningBehavior.UNKNOWN)
|
||||||
|
return capabilities;
|
||||||
|
|
||||||
|
capabilities.Remove(Capability.OPTIONAL_REASONING);
|
||||||
|
capabilities.Remove(Capability.REASONING_BY_DEFAULT);
|
||||||
|
capabilities.Remove(Capability.ALWAYS_REASONING);
|
||||||
|
capabilities.Add(reasoningBehavior switch
|
||||||
|
{
|
||||||
|
ModelReasoningBehavior.OPTIONAL => Capability.OPTIONAL_REASONING,
|
||||||
|
ModelReasoningBehavior.DEFAULT_ON => Capability.REASONING_BY_DEFAULT,
|
||||||
|
ModelReasoningBehavior.ALWAYS_ON => Capability.ALWAYS_REASONING,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(reasoningBehavior), reasoningBehavior, null),
|
||||||
|
});
|
||||||
|
|
||||||
|
return capabilities;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -78,4 +100,4 @@ public static partial class ProviderExtensions
|
|||||||
|
|
||||||
return capabilities;
|
return capabilities;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -146,21 +146,55 @@ public static partial class ProviderExtensions
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
private static ReasoningConfigurationState GetOpenAICompatibleReasoningState(IDictionary<string, object> parameters)
|
private static ReasoningConfigurationState GetOpenAICompatibleReasoningState(IDictionary<string, object> parameters)
|
||||||
{
|
{
|
||||||
var reasoningState = ReasoningConfigurationState.NOT_CONFIGURED;
|
var states = new List<ReasoningConfigurationState>();
|
||||||
if (TryGetParameter(parameters, "reasoning", out var reasoning))
|
if (TryGetParameter(parameters, "reasoning", out var reasoning))
|
||||||
{
|
{
|
||||||
reasoningState = reasoning switch
|
if (reasoning is IDictionary<string, object> reasoningObject)
|
||||||
{
|
{
|
||||||
IDictionary<string, object> reasoningObject when TryGetParameter(reasoningObject, "effort", out var effort) => GetLevelState(effort),
|
if (TryGetParameter(reasoningObject, "effort", out var effort))
|
||||||
IDictionary<string, object> reasoningObject when TryGetParameter(reasoningObject, "summary", out var summary) => GetLevelState(summary),
|
states.Add(GetLevelState(effort));
|
||||||
IDictionary<string, object> => ReasoningConfigurationState.NOT_CONFIGURED,
|
|
||||||
_ => GetLevelState(reasoning),
|
if (TryGetParameter(reasoningObject, "enabled", out var enabled))
|
||||||
};
|
states.Add(GetLevelState(enabled));
|
||||||
|
|
||||||
|
if (TryGetParameter(reasoningObject, "max_tokens", out var maxTokens))
|
||||||
|
states.Add(GetBudgetState(maxTokens));
|
||||||
|
|
||||||
|
if (TryGetParameter(reasoningObject, "summary", out var summary))
|
||||||
|
states.Add(GetReasoningSummaryState(summary));
|
||||||
|
|
||||||
|
if (TryGetParameter(reasoningObject, "generate_summary", out var generateSummary))
|
||||||
|
states.Add(GetReasoningSummaryState(generateSummary));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
states.Add(GetLevelState(reasoning));
|
||||||
}
|
}
|
||||||
|
|
||||||
return MergeReasoningStates(reasoningState, GetReasoningEffortState(parameters));
|
if (TryGetParameter(parameters, "include_reasoning", out var includeReasoning) &&
|
||||||
|
GetLevelState(includeReasoning) is ReasoningConfigurationState.EXPLICITLY_ENABLED)
|
||||||
|
states.Add(ReasoningConfigurationState.EXPLICITLY_ENABLED);
|
||||||
|
|
||||||
|
states.Add(GetReasoningEffortState(parameters));
|
||||||
|
return MergeReasoningStates(states);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Detect summary settings that imply reasoning is enabled.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Turning a summary off controls visibility only and does not disable the model's reasoning.
|
||||||
|
/// </remarks>
|
||||||
|
private static ReasoningConfigurationState GetReasoningSummaryState(object? value) => value switch
|
||||||
|
{
|
||||||
|
string text when text.Equals("auto", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
text.Equals("concise", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
text.Equals("detailed", StringComparison.OrdinalIgnoreCase)
|
||||||
|
=> ReasoningConfigurationState.EXPLICITLY_ENABLED,
|
||||||
|
|
||||||
|
true => ReasoningConfigurationState.EXPLICITLY_ENABLED,
|
||||||
|
_ => ReasoningConfigurationState.NOT_CONFIGURED,
|
||||||
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Detect a top-level <c>reasoning_effort</c> parameter.
|
/// Detect a top-level <c>reasoning_effort</c> parameter.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -444,6 +478,7 @@ public static partial class ProviderExtensions
|
|||||||
text.Equals("minimal", StringComparison.OrdinalIgnoreCase) ||
|
text.Equals("minimal", StringComparison.OrdinalIgnoreCase) ||
|
||||||
text.Equals("medium", StringComparison.OrdinalIgnoreCase) ||
|
text.Equals("medium", StringComparison.OrdinalIgnoreCase) ||
|
||||||
text.Equals("high", StringComparison.OrdinalIgnoreCase) ||
|
text.Equals("high", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
text.Equals("xhigh", StringComparison.OrdinalIgnoreCase) ||
|
||||||
text.Equals("max", StringComparison.OrdinalIgnoreCase);
|
text.Equals("max", StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -516,4 +551,4 @@ public static partial class ProviderExtensions
|
|||||||
value = parameters[foundKey];
|
value = parameters[foundKey];
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user