Skip to content
Open
Show file tree
Hide file tree
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
15 changes: 15 additions & 0 deletions BotSharp.sln
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Core.Rules", "src\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.DeepSeekAI", "src\Plugins\BotSharp.Plugin.DeepSeekAI\BotSharp.Plugin.DeepSeekAI.csproj", "{AF329442-B48E-4B48-A18A-1C869D1BA6F5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.MiniMaxAI", "src\Plugins\BotSharp.Plugin.MiniMaxAI\BotSharp.Plugin.MiniMaxAI.csproj", "{D92C1A49-3F64-4D0C-91C0-B14EA47D2503}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Core.Realtime", "src\Infrastructure\BotSharp.Core.Realtime\BotSharp.Core.Realtime.csproj", "{781F1465-365C-0F22-1775-25025DAFA4C7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Test.ComputerUse", "tests\BotSharp.Test.ComputerUse\BotSharp.Test.ComputerUse.csproj", "{C19D9AC1-97DD-8E65-E8DB-D295A095AA2D}"
Expand Down Expand Up @@ -737,6 +739,18 @@ Global
{AF329442-B48E-4B48-A18A-1C869D1BA6F5}.Release|x64.Build.0 = Release|Any CPU
{AF329442-B48E-4B48-A18A-1C869D1BA6F5}.Release|x86.ActiveCfg = Release|Any CPU
{AF329442-B48E-4B48-A18A-1C869D1BA6F5}.Release|x86.Build.0 = Release|Any CPU
{D92C1A49-3F64-4D0C-91C0-B14EA47D2503}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D92C1A49-3F64-4D0C-91C0-B14EA47D2503}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D92C1A49-3F64-4D0C-91C0-B14EA47D2503}.Debug|x64.ActiveCfg = Debug|Any CPU
{D92C1A49-3F64-4D0C-91C0-B14EA47D2503}.Debug|x64.Build.0 = Debug|Any CPU
{D92C1A49-3F64-4D0C-91C0-B14EA47D2503}.Debug|x86.ActiveCfg = Debug|Any CPU
{D92C1A49-3F64-4D0C-91C0-B14EA47D2503}.Debug|x86.Build.0 = Debug|Any CPU
{D92C1A49-3F64-4D0C-91C0-B14EA47D2503}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D92C1A49-3F64-4D0C-91C0-B14EA47D2503}.Release|Any CPU.Build.0 = Release|Any CPU
{D92C1A49-3F64-4D0C-91C0-B14EA47D2503}.Release|x64.ActiveCfg = Release|Any CPU
{D92C1A49-3F64-4D0C-91C0-B14EA47D2503}.Release|x64.Build.0 = Release|Any CPU
{D92C1A49-3F64-4D0C-91C0-B14EA47D2503}.Release|x86.ActiveCfg = Release|Any CPU
{D92C1A49-3F64-4D0C-91C0-B14EA47D2503}.Release|x86.Build.0 = Release|Any CPU
{781F1465-365C-0F22-1775-25025DAFA4C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{781F1465-365C-0F22-1775-25025DAFA4C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{781F1465-365C-0F22-1775-25025DAFA4C7}.Debug|x64.ActiveCfg = Debug|Any CPU
Expand Down Expand Up @@ -1001,6 +1015,7 @@ Global
{F812BAAE-5A7D-4DF7-8E71-70696B51C61F} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749}
{AFD64412-4D6A-452E-82A2-79E5D8842E29} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749}
{AF329442-B48E-4B48-A18A-1C869D1BA6F5} = {D5293208-2BEF-42FC-A64C-5954F61720BA}
{D92C1A49-3F64-4D0C-91C0-B14EA47D2503} = {D5293208-2BEF-42FC-A64C-5954F61720BA}
{781F1465-365C-0F22-1775-25025DAFA4C7} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749}
{C19D9AC1-97DD-8E65-E8DB-D295A095AA2D} = {32FAFFFE-A4CB-4FEE-BF7C-84518BBC6DCC}
{B268E2F0-060F-8466-7D81-ABA4D735CA59} = {51AFE054-AE99-497D-A593-69BAEFB5106F}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ public class TokenStatsModel
public string Provider { get; set; }
public string Model { get; set; }
public string Prompt { get; set; }
public string? ServiceTier { get; set; }

#region Text token
public long TextInputTokens { get; set; }
public long CachedTextInputTokens { get; set; }
public long CachedTextInputWriteTokens { get; set; }
public long TextOutputTokens { get; set; }
#endregion

Expand All @@ -29,7 +31,8 @@ public class TokenStatsModel
public float ImageGenerationUnitCost { get; set; }
#endregion

public long TotalInputTokens => TextInputTokens + CachedTextInputTokens
public long TotalTextInputTokens => TextInputTokens + CachedTextInputTokens + CachedTextInputWriteTokens;
public long TotalInputTokens => TotalTextInputTokens
+ AudioInputTokens + CachedAudioInputTokens
+ ImageInputTokens + CachedImageInputTokens;
public long TotalOutputTokens => TextOutputTokens + AudioOutputTokens + ImageOutputTokens;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ public class LlmModelSetting

public string ApiKey { get; set; } = null!;
public string? Endpoint { get; set; }
public long? ContextWindow { get; set; }
public LlmModelType Type { get; set; } = LlmModelType.Chat;
public List<LlmModelCapability> Capabilities { get; set; } = [];
public List<string> InputModalities { get; set; } = [];

/// <summary>
/// If true, allow sending images/videos to this model
Expand Down Expand Up @@ -183,7 +185,10 @@ public class LlmCostSetting
#region Text token
public float TextInputCost { get; set; } = 0f;
public float CachedTextInputCost { get; set; } = 0f;
public float CachedTextInputWriteCost { get; set; } = 0f;
public float TextOutputCost { get; set; } = 0f;
public string DefaultServiceTier { get; set; } = "standard";
public IList<LlmTextTokenCostTier>? TextTokenCostTiers { get; set; }
#endregion

#region Audio token
Expand All @@ -201,6 +206,29 @@ public class LlmCostSetting
#region Image
public IList<LlmImageCost>? ImageCosts { get; set; }
#endregion

public LlmTextTokenCostTier? GetTextTokenCostTier(long inputTokens, string? serviceTier = null)
{
var selectedServiceTier = string.IsNullOrWhiteSpace(serviceTier)
? DefaultServiceTier
: serviceTier;

return TextTokenCostTiers?.FirstOrDefault(x =>
string.Equals(x.ServiceTier, selectedServiceTier, StringComparison.OrdinalIgnoreCase)
&& (!x.InputTokensGreaterThan.HasValue || inputTokens > x.InputTokensGreaterThan.Value)
&& (!x.InputTokensLessThanOrEqual.HasValue || inputTokens <= x.InputTokensLessThanOrEqual.Value));
}
}

public class LlmTextTokenCostTier
{
public string ServiceTier { get; set; } = "standard";
public long? InputTokensGreaterThan { get; set; }
public long? InputTokensLessThanOrEqual { get; set; }
public float TextInputCost { get; set; }
public float CachedTextInputCost { get; set; }
public float CachedTextInputWriteCost { get; set; }
public float TextOutputCost { get; set; }
}

public class LlmImageCost
Expand Down Expand Up @@ -240,4 +268,4 @@ public enum LlmModelCapability
Realtime = 11,
WebSearch = 12,
PdfReading = 13
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,15 @@ public async Task AddToken(TokenStatsModel stats, RoleDialogModel message)
var settings = settingsService.GetSetting(stats.Provider, _model);

#region Text tokens
var deltaTextInputCost = GetDeltaCost(stats.TextInputTokens, settings?.Cost?.TextInputCost);
var deltaCachedTextInputCost = GetDeltaCost(stats.CachedTextInputTokens, settings?.Cost?.CachedTextInputCost);
var deltaTextOutputCost = GetDeltaCost(stats.TextOutputTokens, settings?.Cost?.TextOutputCost);
var textCostTier = settings?.Cost?.GetTextTokenCostTier(stats.TotalTextInputTokens, stats.ServiceTier);
var deltaTextInputCost = GetDeltaCost(stats.TextInputTokens,
textCostTier?.TextInputCost ?? settings?.Cost?.TextInputCost);
var deltaCachedTextInputCost = GetDeltaCost(stats.CachedTextInputTokens,
textCostTier?.CachedTextInputCost ?? settings?.Cost?.CachedTextInputCost);
var deltaCachedTextInputWriteCost = GetDeltaCost(stats.CachedTextInputWriteTokens,
textCostTier?.CachedTextInputWriteCost ?? settings?.Cost?.CachedTextInputWriteCost);
var deltaTextOutputCost = GetDeltaCost(stats.TextOutputTokens,
textCostTier?.TextOutputCost ?? settings?.Cost?.TextOutputCost);
#endregion

#region Audio tokens
Expand All @@ -63,7 +69,7 @@ public async Task AddToken(TokenStatsModel stats, RoleDialogModel message)
#endregion


var deltaPromptCost = deltaTextInputCost + deltaCachedTextInputCost
var deltaPromptCost = deltaTextInputCost + deltaCachedTextInputCost + deltaCachedTextInputWriteCost
+ deltaAudioInputCost + deltaCachedAudioInputCost
+ deltaImageInputCost + deltaCachedImageInputCost;
var deltaCompletionCost = deltaTextOutputCost + deltaAudioOutputCost + deltaImageOutputCost;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace BotSharp.Plugin.AnthropicAI.Providers;

public class ChatCompletionProvider : IChatCompletion
{
public string Provider => "anthropic";
public virtual string Provider => "anthropic";
public string Model => _model;

protected readonly AnthropicSettings _settings;
Expand Down Expand Up @@ -94,7 +94,10 @@ public async Task<RoleDialogModel> GetChatCompletions(Agent agent, List<RoleDial
Prompt = prompt,
Provider = Provider,
Model = _model,
ServiceTier = tokenUsage?.ServiceTier.ToString(),
TextInputTokens = tokenUsage?.InputTokens ?? 0,
CachedTextInputTokens = tokenUsage?.CacheReadInputTokens ?? 0,
CachedTextInputWriteTokens = tokenUsage?.CacheCreationInputTokens ?? 0,
TextOutputTokens = tokenUsage?.OutputTokens ?? 0
});
}
Expand Down Expand Up @@ -172,7 +175,10 @@ public async Task<bool> GetChatCompletionsAsync(Agent agent, List<RoleDialogMode
Prompt = prompt,
Provider = Provider,
Model = _model,
ServiceTier = tokenUsage?.ServiceTier.ToString(),
TextInputTokens = tokenUsage?.InputTokens ?? 0,
CachedTextInputTokens = tokenUsage?.CacheReadInputTokens ?? 0,
CachedTextInputWriteTokens = tokenUsage?.CacheCreationInputTokens ?? 0,
TextOutputTokens = tokenUsage?.OutputTokens ?? 0
});
}
Expand Down Expand Up @@ -321,7 +327,10 @@ public async Task<RoleDialogModel> GetChatCompletionsStreamingAsync(Agent agent,
Prompt = prompt,
Provider = Provider,
Model = _model,
ServiceTier = tokenUsage?.ServiceTier.ToString(),
TextInputTokens = tokenUsage?.InputTokens ?? 0,
CachedTextInputTokens = tokenUsage?.CacheReadInputTokens ?? 0,
CachedTextInputWriteTokens = tokenUsage?.CacheCreationInputTokens ?? 0,
TextOutputTokens = tokenUsage?.OutputTokens ?? 0
});
}
Expand Down Expand Up @@ -465,8 +474,25 @@ public void SetModelName(string model)
return null;
}

var thinking = new ThinkingParameters();
var param = settings.Reasoning.Parameters!;
var thinkingType = LlmUtility.GetModelParameter(
param,
"ThinkingType",
_state.GetState("thinking_type"));
if (thinkingType.IsEqualTo("disabled"))
{
return null;
}

var thinking = new ThinkingParameters();
if (thinkingType.IsEqualTo("adaptive"))
{
thinking.Type = ThinkingType.adaptive;
}
else if (thinkingType.IsEqualTo("enabled"))
{
thinking.Type = ThinkingType.enabled;
}

var bt = _state.GetState("budget_tokens");
if (int.TryParse(bt, out var budgetTokens)
Expand All @@ -484,7 +510,11 @@ public void SetModelName(string model)
thinking.UseInterleavedThinking = useInterleavedThinking;
}

return thinking.BudgetTokens > 0 ? thinking : null;
return thinkingType.IsEqualTo("adaptive")
|| thinkingType.IsEqualTo("enabled")
|| thinking.BudgetTokens > 0
? thinking
: null;
}

private string GetPrompt(MessageParameters parameters)
Expand Down Expand Up @@ -576,4 +606,4 @@ private void CollectMessageContentParts(List<ContentBase> contentParts, List<Bot
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ public static AnthropicClient GetAnthropicClient(string provider, string model,
var settingsService = services.GetRequiredService<ILlmProviderService>();
var settings = settingsService.GetSetting(provider, model);
var client = new AnthropicClient(new APIAuthentication(settings.ApiKey));
if (!string.IsNullOrWhiteSpace(settings.Endpoint))
{
client.ApiUrlFormat = $"{settings.Endpoint.TrimEnd('/')}/{{0}}/{{1}}";
}
return client;
}
}
1 change: 1 addition & 0 deletions src/Plugins/BotSharp.Plugin.AnthropicAI/Using.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
global using BotSharp.Abstraction.Hooks;
global using BotSharp.Abstraction.MessageHub.Models;
global using BotSharp.Abstraction.MLTasks.Settings;
global using BotSharp.Abstraction.MLTasks.Utilities;
global using BotSharp.Abstraction.Options;
global using BotSharp.Abstraction.Utilities;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>$(TargetFramework)</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>$(LangVersion)</LangVersion>
<VersionPrefix>$(BotSharpVersion)</VersionPrefix>
<GeneratePackageOnBuild>$(GeneratePackageOnBuild)</GeneratePackageOnBuild>
<GenerateDocumentationFile>$(GenerateDocumentationFile)</GenerateDocumentationFile>
<OutputPath>$(SolutionDir)packages</OutputPath>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\BotSharp.Plugin.AnthropicAI\BotSharp.Plugin.AnthropicAI.csproj" />
<ProjectReference Include="..\BotSharp.Plugin.OpenAI\BotSharp.Plugin.OpenAI.csproj" />
</ItemGroup>

</Project>
23 changes: 23 additions & 0 deletions src/Plugins/BotSharp.Plugin.MiniMaxAI/MiniMaxAiPlugin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using AnthropicChatCompletionProvider = BotSharp.Plugin.MiniMaxAI.Providers.Chat.AnthropicChatCompletionProvider;
using AnthropicCnChatCompletionProvider = BotSharp.Plugin.MiniMaxAI.Providers.Chat.AnthropicCnChatCompletionProvider;
using OpenAiChatCompletionProvider = BotSharp.Plugin.MiniMaxAI.Providers.Chat.ChatCompletionProvider;
using OpenAiCnChatCompletionProvider = BotSharp.Plugin.MiniMaxAI.Providers.Chat.OpenAiCnChatCompletionProvider;

namespace BotSharp.Plugin.MiniMaxAI;

[PluginDependency("BotSharp.Plugin.OpenAI", "BotSharp.Plugin.AnthropicAI")]
public class MiniMaxAiPlugin : IBotSharpPlugin
{
public string Id => "8a4ebd68-4d7d-4c5c-aed9-263946cc3a0d";
public string Name => "MiniMax";
public string Description => "MiniMax AI models exposed through the OpenAI-compatible API.";
public string IconUrl => "https://www.minimax.io/favicon.ico";

public void RegisterDI(IServiceCollection services, IConfiguration config)
{
services.AddScoped<IChatCompletion, OpenAiChatCompletionProvider>();
services.AddScoped<IChatCompletion, OpenAiCnChatCompletionProvider>();
services.AddScoped<IChatCompletion, AnthropicChatCompletionProvider>();
services.AddScoped<IChatCompletion, AnthropicCnChatCompletionProvider>();
}
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace BotSharp.Plugin.MiniMaxAI.Providers.Chat;

public class AnthropicChatCompletionProvider : global::BotSharp.Plugin.AnthropicAI.Providers.ChatCompletionProvider
{
public override string Provider => "minimax-anthropic";

public AnthropicChatCompletionProvider(
IServiceProvider services,
ILogger<global::BotSharp.Plugin.AnthropicAI.Providers.ChatCompletionProvider> logger,
IConversationStateService state) : base(new(), services, logger, state)
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace BotSharp.Plugin.MiniMaxAI.Providers.Chat;

public class AnthropicCnChatCompletionProvider : AnthropicChatCompletionProvider
{
public override string Provider => "minimax-anthropic-cn";

public AnthropicCnChatCompletionProvider(
IServiceProvider services,
ILogger<global::BotSharp.Plugin.AnthropicAI.Providers.ChatCompletionProvider> logger,
IConversationStateService state) : base(services, logger, state)
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using OpenAI.Chat;

namespace BotSharp.Plugin.MiniMaxAI.Providers.Chat;

#pragma warning disable SCME0001

public class ChatCompletionProvider : global::BotSharp.Plugin.OpenAI.Providers.Chat.ChatCompletionProvider
{
public override string Provider => "minimax";
protected override bool UseResponseApi => false;

Comment thread
qodo-code-review[bot] marked this conversation as resolved.
public ChatCompletionProvider(
ILogger<global::BotSharp.Plugin.OpenAI.Providers.Chat.ChatCompletionProvider> logger,
IServiceProvider services,
IConversationStateService state,
IFileStorageService fileStorage) : base(new(), logger, services, state, fileStorage)
{
}

protected override void ConfigureChatCompletionOptions(
ChatCompletionOptions options,
LlmModelSetting? settings)
{
var parameters = settings?.Reasoning?.Parameters;
var thinkingType = LlmUtility.GetModelParameter(
parameters,
"ThinkingType",
_state.GetState("thinking_type"));
if (thinkingType is "adaptive" or "disabled")
{
var thinking = JsonSerializer.SerializeToUtf8Bytes(new { type = thinkingType });
options.Patch.Set("$.thinking"u8, BinaryData.FromBytes(thinking));
}

var serviceTiers = settings?.Cost?.TextTokenCostTiers?
.Select(x => x.ServiceTier)
.Distinct(StringComparer.OrdinalIgnoreCase);
var serviceTier = LlmUtility.VerifyModelParameter(
_state.GetState("service_tier"),
settings?.Cost?.DefaultServiceTier,
serviceTiers);
if (!string.IsNullOrWhiteSpace(serviceTier))
{
options.Patch.Set("$.service_tier"u8, serviceTier);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace BotSharp.Plugin.MiniMaxAI.Providers.Chat;

public class OpenAiCnChatCompletionProvider : ChatCompletionProvider
{
public override string Provider => "minimax-cn";

public OpenAiCnChatCompletionProvider(
ILogger<global::BotSharp.Plugin.OpenAI.Providers.Chat.ChatCompletionProvider> logger,
IServiceProvider services,
IConversationStateService state,
IFileStorageService fileStorage) : base(logger, services, state, fileStorage)
{
}
}
Loading