From 41cbf124ae76e636b4bf2c47c96b00a416f0885f Mon Sep 17 00:00:00 2001 From: Test User Date: Thu, 19 Mar 2026 17:00:46 +0800 Subject: [PATCH 1/6] fix: change replyToMessageId from long? to int? to match ISendPhotoToolService interface --- TelegramSearchBot/Service/Tools/SendPhotoToolService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TelegramSearchBot/Service/Tools/SendPhotoToolService.cs b/TelegramSearchBot/Service/Tools/SendPhotoToolService.cs index eef6ea8f..40ce33ae 100644 --- a/TelegramSearchBot/Service/Tools/SendPhotoToolService.cs +++ b/TelegramSearchBot/Service/Tools/SendPhotoToolService.cs @@ -41,7 +41,7 @@ public async Task SendPhotoBase64( [BuiltInParameter("The base64 encoded image data (without the data URI prefix).")] string base64Data, ToolContext toolContext, [BuiltInParameter("Optional caption for the photo (max 1024 characters).", IsRequired = false)] string caption = null, - [BuiltInParameter("Optional message ID to reply to.", IsRequired = false)] long? replyToMessageId = null) { + [BuiltInParameter("Optional message ID to reply to.", IsRequired = false)] int? replyToMessageId = null) { try { byte[] imageBytes; try { @@ -94,7 +94,7 @@ public async Task SendPhotoFile( [BuiltInParameter("The file path to the image on the server.")] string filePath, ToolContext toolContext, [BuiltInParameter("Optional caption for the photo (max 1024 characters).", IsRequired = false)] string caption = null, - [BuiltInParameter("Optional message ID to reply to.", IsRequired = false)] long? replyToMessageId = null) { + [BuiltInParameter("Optional message ID to reply to.", IsRequired = false)] int? replyToMessageId = null) { try { if (string.IsNullOrWhiteSpace(filePath)) { return new SendPhotoResult { From 3e5536b77154422a53df8b415d90f3fd1739d683 Mon Sep 17 00:00:00 2001 From: Test User Date: Fri, 20 Mar 2026 08:03:27 +0800 Subject: [PATCH 2/6] feat: add send_video_file and send_document_file MCP tools Also fix pre-existing long->int conversion bugs in SendPhotoToolService and LLMIterationCallbackController --- .../Model/Tools/SendDocumentResult.cs | 8 ++ .../Model/Tools/SendVideoResult.cs | 8 ++ .../AI/LLM/LLMIterationCallbackController.cs | 4 +- .../Tools/ISendDocumentToolService.cs | 13 +++ .../Interface/Tools/ISendVideoToolService.cs | 13 +++ .../Service/Tools/SendDocumentToolService.cs | 101 ++++++++++++++++++ .../Service/Tools/SendPhotoToolService.cs | 6 +- .../Service/Tools/SendVideoToolService.cs | 101 ++++++++++++++++++ 8 files changed, 249 insertions(+), 5 deletions(-) create mode 100644 TelegramSearchBot.Common/Model/Tools/SendDocumentResult.cs create mode 100644 TelegramSearchBot.Common/Model/Tools/SendVideoResult.cs create mode 100644 TelegramSearchBot/Interface/Tools/ISendDocumentToolService.cs create mode 100644 TelegramSearchBot/Interface/Tools/ISendVideoToolService.cs create mode 100644 TelegramSearchBot/Service/Tools/SendDocumentToolService.cs create mode 100644 TelegramSearchBot/Service/Tools/SendVideoToolService.cs diff --git a/TelegramSearchBot.Common/Model/Tools/SendDocumentResult.cs b/TelegramSearchBot.Common/Model/Tools/SendDocumentResult.cs new file mode 100644 index 00000000..1a770e3e --- /dev/null +++ b/TelegramSearchBot.Common/Model/Tools/SendDocumentResult.cs @@ -0,0 +1,8 @@ +namespace TelegramSearchBot.Model.Tools { + public class SendDocumentResult { + public bool Success { get; set; } + public int? MessageId { get; set; } + public long ChatId { get; set; } + public string Error { get; set; } + } +} diff --git a/TelegramSearchBot.Common/Model/Tools/SendVideoResult.cs b/TelegramSearchBot.Common/Model/Tools/SendVideoResult.cs new file mode 100644 index 00000000..921808a4 --- /dev/null +++ b/TelegramSearchBot.Common/Model/Tools/SendVideoResult.cs @@ -0,0 +1,8 @@ +namespace TelegramSearchBot.Model.Tools { + public class SendVideoResult { + public bool Success { get; set; } + public int? MessageId { get; set; } + public long ChatId { get; set; } + public string Error { get; set; } + } +} diff --git a/TelegramSearchBot/Controller/AI/LLM/LLMIterationCallbackController.cs b/TelegramSearchBot/Controller/AI/LLM/LLMIterationCallbackController.cs index 268df2ee..22f6d48c 100644 --- a/TelegramSearchBot/Controller/AI/LLM/LLMIterationCallbackController.cs +++ b/TelegramSearchBot/Controller/AI/LLM/LLMIterationCallbackController.cs @@ -154,7 +154,7 @@ await _botClient.EditMessageReplyMarkup( List sentMessagesForDb = await _sendMessageService.SendDraftStream( resumeStream, snapshot.ChatId, - snapshot.OriginalMessageId, + (int)snapshot.OriginalMessageId, initialContent, CancellationToken.None ); @@ -198,7 +198,7 @@ await _botClient.SendMessage( snapshot.ChatId, $"⚠️ AI 再次达到最大迭代次数限制({Env.MaxToolCycles} 次),已完成 {executionContext.SnapshotData.CyclesSoFar} 次循环,是否继续迭代?", replyMarkup: keyboard, - replyParameters: new ReplyParameters { MessageId = snapshot.OriginalMessageId } + replyParameters: new ReplyParameters { MessageId = (int)snapshot.OriginalMessageId } ); } } finally { diff --git a/TelegramSearchBot/Interface/Tools/ISendDocumentToolService.cs b/TelegramSearchBot/Interface/Tools/ISendDocumentToolService.cs new file mode 100644 index 00000000..5c8dde24 --- /dev/null +++ b/TelegramSearchBot/Interface/Tools/ISendDocumentToolService.cs @@ -0,0 +1,13 @@ +using System.Threading.Tasks; +using TelegramSearchBot.Model; +using TelegramSearchBot.Model.Tools; + +namespace TelegramSearchBot.Interface.Tools { + public interface ISendDocumentToolService { + Task SendDocumentFile( + string filePath, + ToolContext toolContext, + string caption = null, + int? replyToMessageId = null); + } +} diff --git a/TelegramSearchBot/Interface/Tools/ISendVideoToolService.cs b/TelegramSearchBot/Interface/Tools/ISendVideoToolService.cs new file mode 100644 index 00000000..8d275d9c --- /dev/null +++ b/TelegramSearchBot/Interface/Tools/ISendVideoToolService.cs @@ -0,0 +1,13 @@ +using System.Threading.Tasks; +using TelegramSearchBot.Model; +using TelegramSearchBot.Model.Tools; + +namespace TelegramSearchBot.Interface.Tools { + public interface ISendVideoToolService { + Task SendVideoFile( + string filePath, + ToolContext toolContext, + string caption = null, + int? replyToMessageId = null); + } +} diff --git a/TelegramSearchBot/Service/Tools/SendDocumentToolService.cs b/TelegramSearchBot/Service/Tools/SendDocumentToolService.cs new file mode 100644 index 00000000..b22a522f --- /dev/null +++ b/TelegramSearchBot/Service/Tools/SendDocumentToolService.cs @@ -0,0 +1,101 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Telegram.Bot; +using Telegram.Bot.Types; +using Telegram.Bot.Types.Enums; +using TelegramSearchBot.Attributes; +using TelegramSearchBot.Interface; +using TelegramSearchBot.Interface.Tools; +using TelegramSearchBot.Manager; +using TelegramSearchBot.Model; +using TelegramSearchBot.Model.Tools; + +namespace TelegramSearchBot.Service.Tools { + [Injectable(Microsoft.Extensions.DependencyInjection.ServiceLifetime.Transient)] + public class SendDocumentToolService : IService, ISendDocumentToolService { + public string ServiceName => "SendDocumentToolService"; + + private readonly ITelegramBotClient _botClient; + private readonly SendMessage _sendMessage; + + private static readonly TimeSpan SendTimeout = TimeSpan.FromSeconds(120); + + public SendDocumentToolService(ITelegramBotClient botClient, SendMessage sendMessage) { + _botClient = botClient; + _sendMessage = sendMessage; + } + + /// + /// Resolves the effective reply-to message ID. Uses the explicitly provided value if set, + /// otherwise falls back to the ToolContext.MessageId (the original user message). + /// + private static ReplyParameters GetReplyParameters(int? explicitReplyToMessageId, ToolContext toolContext) { + long? messageId = explicitReplyToMessageId ?? (toolContext.MessageId != 0 ? toolContext.MessageId : (long?)null); + return messageId.HasValue ? new ReplyParameters { MessageId = (int)messageId.Value } : null; + } + + [BuiltInTool("Sends a document/file to the current chat using a file path.", Name = "send_document_file")] + public async Task SendDocumentFile( + [BuiltInParameter("The file path to the document on the server.")] string filePath, + ToolContext toolContext, + [BuiltInParameter("Optional caption for the document (max 1024 characters).", IsRequired = false)] string caption = null, + [BuiltInParameter("Optional message ID to reply to.", IsRequired = false)] int? replyToMessageId = null) { + try { + if (string.IsNullOrWhiteSpace(filePath)) { + return new SendDocumentResult { + Success = false, + ChatId = toolContext.ChatId, + Error = "File path cannot be empty." + }; + } + + if (!File.Exists(filePath)) { + return new SendDocumentResult { + Success = false, + ChatId = toolContext.ChatId, + Error = $"File not found: {filePath}" + }; + } + + var fileInfo = new FileInfo(filePath); + const long maxFileSizeBytes = 2L * 1024 * 1024 * 1024; + if (fileInfo.Length > maxFileSizeBytes) { + return new SendDocumentResult { + Success = false, + ChatId = toolContext.ChatId, + Error = $"File is too large ({fileInfo.Length / 1024 / 1024 / 1024}GB). Maximum allowed size is 2GB." + }; + } + + var fileBytes = await File.ReadAllBytesAsync(filePath); + var document = InputFile.FromStream(new MemoryStream(fileBytes), fileInfo.Name); + + var replyParameters = GetReplyParameters(replyToMessageId, toolContext); + + using var cts = new CancellationTokenSource(SendTimeout); + var message = await _sendMessage.AddTaskWithResult(async () => await _botClient.SendDocument( + chatId: toolContext.ChatId, + document: document, + caption: string.IsNullOrEmpty(caption) ? null : caption.Length > 1024 ? caption.Substring(0, 1024) : caption, + parseMode: ParseMode.Html, + replyParameters: replyParameters, + cancellationToken: cts.Token + ), toolContext.ChatId); + + return new SendDocumentResult { + Success = true, + MessageId = message.MessageId, + ChatId = message.Chat.Id + }; + } catch (Exception ex) { + return new SendDocumentResult { + Success = false, + ChatId = toolContext.ChatId, + Error = $"Failed to send document: {ex.Message}" + }; + } + } + } +} diff --git a/TelegramSearchBot/Service/Tools/SendPhotoToolService.cs b/TelegramSearchBot/Service/Tools/SendPhotoToolService.cs index 40ce33ae..a91387e7 100644 --- a/TelegramSearchBot/Service/Tools/SendPhotoToolService.cs +++ b/TelegramSearchBot/Service/Tools/SendPhotoToolService.cs @@ -31,9 +31,9 @@ public SendPhotoToolService(ITelegramBotClient botClient, SendMessage sendMessag /// Resolves the effective reply-to message ID. Uses the explicitly provided value if set, /// otherwise falls back to the ToolContext.MessageId (the original user message). /// - private static ReplyParameters GetReplyParameters(long? explicitReplyToMessageId, ToolContext toolContext) { - var messageId = explicitReplyToMessageId ?? (toolContext.MessageId != 0 ? toolContext.MessageId : (long?)null); - return messageId.HasValue ? new ReplyParameters { MessageId = messageId.Value } : null; + private static ReplyParameters GetReplyParameters(int? explicitReplyToMessageId, ToolContext toolContext) { + long? messageId = explicitReplyToMessageId ?? (toolContext.MessageId != 0 ? toolContext.MessageId : (long?)null); + return messageId.HasValue ? new ReplyParameters { MessageId = (int)messageId.Value } : null; } [BuiltInTool("Sends a photo to the current chat using base64 encoded image data.", Name = "send_photo_base64")] diff --git a/TelegramSearchBot/Service/Tools/SendVideoToolService.cs b/TelegramSearchBot/Service/Tools/SendVideoToolService.cs new file mode 100644 index 00000000..4e33ba8b --- /dev/null +++ b/TelegramSearchBot/Service/Tools/SendVideoToolService.cs @@ -0,0 +1,101 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Telegram.Bot; +using Telegram.Bot.Types; +using Telegram.Bot.Types.Enums; +using TelegramSearchBot.Attributes; +using TelegramSearchBot.Interface; +using TelegramSearchBot.Interface.Tools; +using TelegramSearchBot.Manager; +using TelegramSearchBot.Model; +using TelegramSearchBot.Model.Tools; + +namespace TelegramSearchBot.Service.Tools { + [Injectable(Microsoft.Extensions.DependencyInjection.ServiceLifetime.Transient)] + public class SendVideoToolService : IService, ISendVideoToolService { + public string ServiceName => "SendVideoToolService"; + + private readonly ITelegramBotClient _botClient; + private readonly SendMessage _sendMessage; + + private static readonly TimeSpan SendTimeout = TimeSpan.FromSeconds(120); + + public SendVideoToolService(ITelegramBotClient botClient, SendMessage sendMessage) { + _botClient = botClient; + _sendMessage = sendMessage; + } + + /// + /// Resolves the effective reply-to message ID. Uses the explicitly provided value if set, + /// otherwise falls back to the ToolContext.MessageId (the original user message). + /// + private static ReplyParameters GetReplyParameters(int? explicitReplyToMessageId, ToolContext toolContext) { + long? messageId = explicitReplyToMessageId ?? (toolContext.MessageId != 0 ? toolContext.MessageId : (long?)null); + return messageId.HasValue ? new ReplyParameters { MessageId = (int)messageId.Value } : null; + } + + [BuiltInTool("Sends a video to the current chat using a file path.", Name = "send_video_file")] + public async Task SendVideoFile( + [BuiltInParameter("The file path to the video on the server.")] string filePath, + ToolContext toolContext, + [BuiltInParameter("Optional caption for the video (max 1024 characters).", IsRequired = false)] string caption = null, + [BuiltInParameter("Optional message ID to reply to.", IsRequired = false)] int? replyToMessageId = null) { + try { + if (string.IsNullOrWhiteSpace(filePath)) { + return new SendVideoResult { + Success = false, + ChatId = toolContext.ChatId, + Error = "File path cannot be empty." + }; + } + + if (!File.Exists(filePath)) { + return new SendVideoResult { + Success = false, + ChatId = toolContext.ChatId, + Error = $"File not found: {filePath}" + }; + } + + var fileInfo = new FileInfo(filePath); + const long maxFileSizeBytes = 50 * 1024 * 1024; // Telegram video limit: 50 MB + if (fileInfo.Length > maxFileSizeBytes) { + return new SendVideoResult { + Success = false, + ChatId = toolContext.ChatId, + Error = $"File is too large ({fileInfo.Length / 1024 / 1024}MB). Maximum allowed size is 50MB." + }; + } + + var fileBytes = await File.ReadAllBytesAsync(filePath); + var video = InputFile.FromStream(new MemoryStream(fileBytes), fileInfo.Name); + + var replyParameters = GetReplyParameters(replyToMessageId, toolContext); + + using var cts = new CancellationTokenSource(SendTimeout); + var message = await _sendMessage.AddTaskWithResult(async () => await _botClient.SendVideo( + chatId: toolContext.ChatId, + video: video, + caption: string.IsNullOrEmpty(caption) ? null : caption.Length > 1024 ? caption.Substring(0, 1024) : caption, + parseMode: ParseMode.Html, + replyParameters: replyParameters, + cancellationToken: cts.Token + ), toolContext.ChatId); + + return new SendVideoResult { + Success = true, + MessageId = message.MessageId, + ChatId = message.Chat.Id + }; + } catch (Exception ex) { + return new SendVideoResult { + Success = false, + ChatId = toolContext.ChatId, + Error = $"Failed to send video: {ex.Message}" + }; + } + } + } +} From 67a8b44cb0a0ad3d7fd358b40d3496d10ec706a1 Mon Sep 17 00:00:00 2001 From: Test User Date: Fri, 20 Mar 2026 08:43:57 +0800 Subject: [PATCH 3/6] feat: add send_video_file and send_document_file MCP tools Also fix pre-existing long->int conversion bugs and apply formatting fixes --- .../Attributes/McpAttributes.cs | 3 +- .../Tools/IterationLimitReachedPayload.cs | 2 +- ...60303031828_AddUserWithGroupUniqueIndex.cs | 14 ++---- ...0313124507_AddChannelWithModelIsDeleted.cs | 14 ++---- .../Service/AI/LLM/GeneralLLMServiceTests.cs | 29 +++++++++--- .../AI/LLM/ModelCapabilityServiceTests.cs | 46 +++++++++++++------ ...OpenAIProviderHistorySerializationTests.cs | 2 +- .../Service/AI/LLM/McpToolHelper.cs | 9 ++-- .../Service/AI/LLM/OpenAIService.cs | 6 +-- .../Service/Mcp/McpClient.cs | 2 +- .../Service/Mcp/McpServerManager.cs | 4 +- .../Service/Tools/FileToolService.cs | 6 +-- .../Helper/WordCloudHelperTests.cs | 4 +- .../AI/LLM/LLMIterationCallbackController.cs | 4 +- .../Tools/ISendDocumentToolService.cs | 2 +- .../Interface/Tools/ISendVideoToolService.cs | 2 +- .../BotAPI/SendMessageService.Streaming.cs | 2 +- .../Service/Storage/MessageService.cs | 10 ++-- .../Service/Tools/SendDocumentToolService.cs | 8 ++-- .../Service/Tools/SendPhotoToolService.cs | 10 ++-- .../Service/Tools/SendVideoToolService.cs | 8 ++-- .../Service/Vector/FaissVectorService.cs | 6 +-- 22 files changed, 109 insertions(+), 84 deletions(-) diff --git a/TelegramSearchBot.Common/Attributes/McpAttributes.cs b/TelegramSearchBot.Common/Attributes/McpAttributes.cs index 519b0001..9a383c34 100644 --- a/TelegramSearchBot.Common/Attributes/McpAttributes.cs +++ b/TelegramSearchBot.Common/Attributes/McpAttributes.cs @@ -1,7 +1,6 @@ using System; -namespace TelegramSearchBot.Attributes -{ +namespace TelegramSearchBot.Attributes { /// /// Marks a method as a tool that can be called by the LLM. /// Deprecated: Use instead for built-in tools. diff --git a/TelegramSearchBot.Common/Model/Tools/IterationLimitReachedPayload.cs b/TelegramSearchBot.Common/Model/Tools/IterationLimitReachedPayload.cs index b605ddef..4dec7584 100644 --- a/TelegramSearchBot.Common/Model/Tools/IterationLimitReachedPayload.cs +++ b/TelegramSearchBot.Common/Model/Tools/IterationLimitReachedPayload.cs @@ -22,7 +22,7 @@ public static bool IsIterationLimitMessage(string content) { /// 在累积内容末尾追加标记 /// public static string AppendMarker(string accumulatedContent) { - return (accumulatedContent ?? string.Empty) + Marker; + return ( accumulatedContent ?? string.Empty ) + Marker; } /// diff --git a/TelegramSearchBot.Database/Migrations/20260303031828_AddUserWithGroupUniqueIndex.cs b/TelegramSearchBot.Database/Migrations/20260303031828_AddUserWithGroupUniqueIndex.cs index 3378f31b..79ea689b 100644 --- a/TelegramSearchBot.Database/Migrations/20260303031828_AddUserWithGroupUniqueIndex.cs +++ b/TelegramSearchBot.Database/Migrations/20260303031828_AddUserWithGroupUniqueIndex.cs @@ -1,15 +1,12 @@ -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable -namespace TelegramSearchBot.Migrations -{ +namespace TelegramSearchBot.Migrations { /// - public partial class AddUserWithGroupUniqueIndex : Migration - { + public partial class AddUserWithGroupUniqueIndex : Migration { /// - protected override void Up(MigrationBuilder migrationBuilder) - { + protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateIndex( name: "IX_UsersWithGroup_UserId_GroupId", table: "UsersWithGroup", @@ -18,8 +15,7 @@ protected override void Up(MigrationBuilder migrationBuilder) } /// - protected override void Down(MigrationBuilder migrationBuilder) - { + protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropIndex( name: "IX_UsersWithGroup_UserId_GroupId", table: "UsersWithGroup"); diff --git a/TelegramSearchBot.Database/Migrations/20260313124507_AddChannelWithModelIsDeleted.cs b/TelegramSearchBot.Database/Migrations/20260313124507_AddChannelWithModelIsDeleted.cs index 045e395e..ccd5f76c 100644 --- a/TelegramSearchBot.Database/Migrations/20260313124507_AddChannelWithModelIsDeleted.cs +++ b/TelegramSearchBot.Database/Migrations/20260313124507_AddChannelWithModelIsDeleted.cs @@ -1,15 +1,12 @@ -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable -namespace TelegramSearchBot.Migrations -{ +namespace TelegramSearchBot.Migrations { /// - public partial class AddChannelWithModelIsDeleted : Migration - { + public partial class AddChannelWithModelIsDeleted : Migration { /// - protected override void Up(MigrationBuilder migrationBuilder) - { + protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.AddColumn( name: "IsDeleted", table: "ChannelsWithModel", @@ -19,8 +16,7 @@ protected override void Up(MigrationBuilder migrationBuilder) } /// - protected override void Down(MigrationBuilder migrationBuilder) - { + protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropColumn( name: "IsDeleted", table: "ChannelsWithModel"); diff --git a/TelegramSearchBot.LLM.Test/Service/AI/LLM/GeneralLLMServiceTests.cs b/TelegramSearchBot.LLM.Test/Service/AI/LLM/GeneralLLMServiceTests.cs index 52ec785b..87e3e109 100644 --- a/TelegramSearchBot.LLM.Test/Service/AI/LLM/GeneralLLMServiceTests.cs +++ b/TelegramSearchBot.LLM.Test/Service/AI/LLM/GeneralLLMServiceTests.cs @@ -102,12 +102,20 @@ public async Task GetChannelsAsync_NoModels_ReturnsEmpty() { public async Task GetChannelsAsync_WithModel_ReturnsOrderedChannels() { // Arrange var channel1 = new LLMChannel { - Name = "ch1", Gateway = "gw1", ApiKey = "key1", - Provider = LLMProvider.OpenAI, Parallel = 2, Priority = 1 + Name = "ch1", + Gateway = "gw1", + ApiKey = "key1", + Provider = LLMProvider.OpenAI, + Parallel = 2, + Priority = 1 }; var channel2 = new LLMChannel { - Name = "ch2", Gateway = "gw2", ApiKey = "key2", - Provider = LLMProvider.OpenAI, Parallel = 3, Priority = 10 + Name = "ch2", + Gateway = "gw2", + ApiKey = "key2", + Provider = LLMProvider.OpenAI, + Parallel = 3, + Priority = 10 }; _dbContext.LLMChannels.AddRange(channel1, channel2); await _dbContext.SaveChangesAsync(); @@ -130,7 +138,10 @@ public async Task GetChannelsAsync_WithModel_ReturnsOrderedChannels() { public async Task ExecAsync_NoModelConfigured_YieldsNoResults() { // Arrange - no group settings configured var message = new TelegramSearchBot.Model.Data.Message { - Content = "test", GroupId = 123, MessageId = 1, FromUserId = 1 + Content = "test", + GroupId = 123, + MessageId = 1, + FromUserId = 1 }; // Act @@ -153,8 +164,12 @@ public async Task GetAvailableCapacityAsync_NoChannels_ReturnsZero() { public async Task GetAvailableCapacityAsync_WithChannels_ReturnsCapacity() { // Arrange var channel = new LLMChannel { - Name = "ch1", Gateway = "gw1", ApiKey = "key1", - Provider = LLMProvider.OpenAI, Parallel = 5, Priority = 1 + Name = "ch1", + Gateway = "gw1", + ApiKey = "key1", + Provider = LLMProvider.OpenAI, + Parallel = 5, + Priority = 1 }; _dbContext.LLMChannels.Add(channel); await _dbContext.SaveChangesAsync(); diff --git a/TelegramSearchBot.LLM.Test/Service/AI/LLM/ModelCapabilityServiceTests.cs b/TelegramSearchBot.LLM.Test/Service/AI/LLM/ModelCapabilityServiceTests.cs index a649e8db..da4ef579 100644 --- a/TelegramSearchBot.LLM.Test/Service/AI/LLM/ModelCapabilityServiceTests.cs +++ b/TelegramSearchBot.LLM.Test/Service/AI/LLM/ModelCapabilityServiceTests.cs @@ -67,8 +67,12 @@ public async Task GetModelCapabilities_NotFound_ReturnsNull() { public async Task GetModelCapabilities_WithCapabilities_ReturnsCorrectModel() { // Arrange var channel = new LLMChannel { - Name = "test", Gateway = "gw", ApiKey = "key", - Provider = LLMProvider.OpenAI, Parallel = 1, Priority = 1 + Name = "test", + Gateway = "gw", + ApiKey = "key", + Provider = LLMProvider.OpenAI, + Parallel = 1, + Priority = 1 }; _dbContext.LLMChannels.Add(channel); await _dbContext.SaveChangesAsync(); @@ -106,8 +110,12 @@ public async Task GetModelCapabilities_WithCapabilities_ReturnsCorrectModel() { public async Task GetToolCallingSupportedModels_ReturnsCorrectModels() { // Arrange var channel = new LLMChannel { - Name = "test", Gateway = "gw", ApiKey = "key", - Provider = LLMProvider.OpenAI, Parallel = 1, Priority = 1 + Name = "test", + Gateway = "gw", + ApiKey = "key", + Provider = LLMProvider.OpenAI, + Parallel = 1, + Priority = 1 }; _dbContext.LLMChannels.Add(channel); await _dbContext.SaveChangesAsync(); @@ -138,7 +146,7 @@ public async Task GetToolCallingSupportedModels_ReturnsCorrectModels() { await _dbContext.SaveChangesAsync(); // Act - var result = (await _service.GetToolCallingSupportedModels()).ToList(); + var result = ( await _service.GetToolCallingSupportedModels() ).ToList(); // Assert Assert.Single(result); @@ -149,8 +157,12 @@ public async Task GetToolCallingSupportedModels_ReturnsCorrectModels() { public async Task GetVisionSupportedModels_ReturnsCorrectModels() { // Arrange var channel = new LLMChannel { - Name = "test", Gateway = "gw", ApiKey = "key", - Provider = LLMProvider.OpenAI, Parallel = 1, Priority = 1 + Name = "test", + Gateway = "gw", + ApiKey = "key", + Provider = LLMProvider.OpenAI, + Parallel = 1, + Priority = 1 }; _dbContext.LLMChannels.Add(channel); await _dbContext.SaveChangesAsync(); @@ -170,7 +182,7 @@ public async Task GetVisionSupportedModels_ReturnsCorrectModels() { await _dbContext.SaveChangesAsync(); // Act - var result = (await _service.GetVisionSupportedModels()).ToList(); + var result = ( await _service.GetVisionSupportedModels() ).ToList(); // Assert Assert.Single(result); @@ -181,8 +193,12 @@ public async Task GetVisionSupportedModels_ReturnsCorrectModels() { public async Task GetEmbeddingModels_ReturnsCorrectModels() { // Arrange var channel = new LLMChannel { - Name = "test", Gateway = "gw", ApiKey = "key", - Provider = LLMProvider.OpenAI, Parallel = 1, Priority = 1 + Name = "test", + Gateway = "gw", + ApiKey = "key", + Provider = LLMProvider.OpenAI, + Parallel = 1, + Priority = 1 }; _dbContext.LLMChannels.Add(channel); await _dbContext.SaveChangesAsync(); @@ -202,7 +218,7 @@ public async Task GetEmbeddingModels_ReturnsCorrectModels() { await _dbContext.SaveChangesAsync(); // Act - var result = (await _service.GetEmbeddingModels()).ToList(); + var result = ( await _service.GetEmbeddingModels() ).ToList(); // Assert Assert.Single(result); @@ -213,8 +229,12 @@ public async Task GetEmbeddingModels_ReturnsCorrectModels() { public async Task CleanupOldCapabilities_RemovesOldEntries() { // Arrange var channel = new LLMChannel { - Name = "test", Gateway = "gw", ApiKey = "key", - Provider = LLMProvider.OpenAI, Parallel = 1, Priority = 1 + Name = "test", + Gateway = "gw", + ApiKey = "key", + Provider = LLMProvider.OpenAI, + Parallel = 1, + Priority = 1 }; _dbContext.LLMChannels.Add(channel); await _dbContext.SaveChangesAsync(); diff --git a/TelegramSearchBot.LLM.Test/Service/AI/LLM/OpenAIProviderHistorySerializationTests.cs b/TelegramSearchBot.LLM.Test/Service/AI/LLM/OpenAIProviderHistorySerializationTests.cs index 3d5b003c..0529dbb2 100644 --- a/TelegramSearchBot.LLM.Test/Service/AI/LLM/OpenAIProviderHistorySerializationTests.cs +++ b/TelegramSearchBot.LLM.Test/Service/AI/LLM/OpenAIProviderHistorySerializationTests.cs @@ -79,7 +79,7 @@ public void SerializeProviderHistory_WithToolCallHistory_PreservesContent() { }; var serialized = OpenAIService.SerializeProviderHistory(history); - + Assert.Equal(5, serialized.Count); Assert.Contains("tool_call", serialized[2].Content); Assert.Contains("bash", serialized[3].Content); diff --git a/TelegramSearchBot.LLM/Service/AI/LLM/McpToolHelper.cs b/TelegramSearchBot.LLM/Service/AI/LLM/McpToolHelper.cs index 402e9de9..9abe16ea 100644 --- a/TelegramSearchBot.LLM/Service/AI/LLM/McpToolHelper.cs +++ b/TelegramSearchBot.LLM/Service/AI/LLM/McpToolHelper.cs @@ -177,7 +177,7 @@ private static string RegisterToolsAndGetPromptString(List assemblies) var builtInParamAttr = param.GetCustomAttribute(); var mcpParamAttr = param.GetCustomAttribute(); var paramDescription = builtInParamAttr?.Description ?? mcpParamAttr?.Description ?? $"Parameter '{param.Name}'"; - var paramIsRequired = builtInParamAttr?.IsRequired ?? mcpParamAttr?.IsRequired ?? (!param.IsOptional && !param.HasDefaultValue); + var paramIsRequired = builtInParamAttr?.IsRequired ?? mcpParamAttr?.IsRequired ?? ( !param.IsOptional && !param.HasDefaultValue ); var paramType = MapToJsonSchemaType(param.ParameterType); properties[param.Name] = new Dictionary { @@ -518,7 +518,7 @@ private static (string toolName, Dictionary arguments) ParseTool } } - if (toolName == null || (!ToolRegistry.ContainsKey(toolName) && !ExternalToolRegistry.ContainsKey(toolName))) { + if (toolName == null || ( !ToolRegistry.ContainsKey(toolName) && !ExternalToolRegistry.ContainsKey(toolName) )) { _sLogger?.LogWarning($"ParseToolElement: Unregistered tool '{element.Name.LocalName}'"); return (null, null); } @@ -713,8 +713,7 @@ public static async Task ExecuteRegisteredToolAsync(string toolName, Dic if (result is Task taskResult) { await taskResult; - if (taskResult.GetType().IsGenericType) - { + if (taskResult.GetType().IsGenericType) { return ( ( dynamic ) taskResult ).Result; } return null; @@ -910,7 +909,7 @@ public static void RegisterExternalMcpTools(Interface.Mcp.IMcpServerManager mcpS RegisterExternalTools( toolInfos, async (serverName, toolName, arguments) => { - var objectArgs = arguments.ToDictionary(kvp => kvp.Key, kvp => (object)kvp.Value); + var objectArgs = arguments.ToDictionary(kvp => kvp.Key, kvp => ( object ) kvp.Value); var result = await mcpServerManager.CallToolAsync(serverName, toolName, objectArgs); if (result.IsError) { return $"Error: {string.Join("\n", result.Content?.Select(c => c.Text ?? "") ?? Enumerable.Empty())}"; diff --git a/TelegramSearchBot.LLM/Service/AI/LLM/OpenAIService.cs b/TelegramSearchBot.LLM/Service/AI/LLM/OpenAIService.cs index 2ec86a11..aee7dc21 100644 --- a/TelegramSearchBot.LLM/Service/AI/LLM/OpenAIService.cs +++ b/TelegramSearchBot.LLM/Service/AI/LLM/OpenAIService.cs @@ -719,10 +719,10 @@ private static bool IsToolCallingNotSupportedError(Exception ex) { } } // Common error patterns when a model/API doesn't support tool calling - return message.Contains("tools", StringComparison.OrdinalIgnoreCase) && - (message.Contains("not supported", StringComparison.OrdinalIgnoreCase) || + return message.Contains("tools", StringComparison.OrdinalIgnoreCase) && + ( message.Contains("not supported", StringComparison.OrdinalIgnoreCase) || message.Contains("unsupported", StringComparison.OrdinalIgnoreCase) || - message.Contains("invalid", StringComparison.OrdinalIgnoreCase)) || + message.Contains("invalid", StringComparison.OrdinalIgnoreCase) ) || message.Contains("unrecognized request argument", StringComparison.OrdinalIgnoreCase); } diff --git a/TelegramSearchBot.LLM/Service/Mcp/McpClient.cs b/TelegramSearchBot.LLM/Service/Mcp/McpClient.cs index f36c45b5..8cf2a9e9 100644 --- a/TelegramSearchBot.LLM/Service/Mcp/McpClient.cs +++ b/TelegramSearchBot.LLM/Service/Mcp/McpClient.cs @@ -179,7 +179,7 @@ private async Task CleanupProcessAsync() { _process.Kill(true); // Wait briefly for the process to actually exit try { - _process.WaitForExit(ProcessExitTimeoutMs); + _process.WaitForExit(ProcessExitTimeoutMs); } catch { } } } catch { } diff --git a/TelegramSearchBot.LLM/Service/Mcp/McpServerManager.cs b/TelegramSearchBot.LLM/Service/Mcp/McpServerManager.cs index d93a8a05..fa4f2cdb 100644 --- a/TelegramSearchBot.LLM/Service/Mcp/McpServerManager.cs +++ b/TelegramSearchBot.LLM/Service/Mcp/McpServerManager.cs @@ -152,7 +152,7 @@ private async Task DisconnectServerAsync(string serverName) { if (_clients.TryRemove(serverName, out var client)) { try { await client.DisconnectAsync(); - (client as IDisposable)?.Dispose(); + ( client as IDisposable )?.Dispose(); } catch (Exception ex) { _logger.LogWarning(ex, "Error disconnecting MCP server '{Name}'", serverName); } @@ -293,7 +293,7 @@ public async Task ShutdownAllAsync() { public void Dispose() { foreach (var kvp in _clients) { try { - (kvp.Value as IDisposable)?.Dispose(); + ( kvp.Value as IDisposable )?.Dispose(); } catch { } } _clients.Clear(); diff --git a/TelegramSearchBot.LLM/Service/Tools/FileToolService.cs b/TelegramSearchBot.LLM/Service/Tools/FileToolService.cs index 4c9a517d..8479e9ff 100644 --- a/TelegramSearchBot.LLM/Service/Tools/FileToolService.cs +++ b/TelegramSearchBot.LLM/Service/Tools/FileToolService.cs @@ -286,7 +286,7 @@ private static string ResolvePath(string path) { private static int CountOccurrences(string text, string pattern) { int count = 0; int index = 0; - while ((index = text.IndexOf(pattern, index, StringComparison.Ordinal)) != -1) { + while (( index = text.IndexOf(pattern, index, StringComparison.Ordinal) ) != -1) { count++; index += pattern.Length; } @@ -296,8 +296,8 @@ private static int CountOccurrences(string text, string pattern) { private static string FormatFileSize(long bytes) { if (bytes < 1024) return $"{bytes}B"; if (bytes < 1024 * 1024) return $"{bytes / 1024.0:F1}KB"; - if (bytes < 1024 * 1024 * 1024) return $"{bytes / (1024.0 * 1024):F1}MB"; - return $"{bytes / (1024.0 * 1024 * 1024):F1}GB"; + if (bytes < 1024 * 1024 * 1024) return $"{bytes / ( 1024.0 * 1024 ):F1}MB"; + return $"{bytes / ( 1024.0 * 1024 * 1024 ):F1}GB"; } } } diff --git a/TelegramSearchBot.Test/Helper/WordCloudHelperTests.cs b/TelegramSearchBot.Test/Helper/WordCloudHelperTests.cs index bbfd566e..a4eac7ac 100644 --- a/TelegramSearchBot.Test/Helper/WordCloudHelperTests.cs +++ b/TelegramSearchBot.Test/Helper/WordCloudHelperTests.cs @@ -33,8 +33,8 @@ public void GenerateWordCloud_ShouldCreateImageFile() { // 打印文件路径 System.Console.WriteLine($"词云图片已保存到: {Path.GetFullPath(outputPath)}"); } catch (Exception ex) when (ex is System.DllNotFoundException || - (ex is System.TypeInitializationException tex && - tex.InnerException is System.PlatformNotSupportedException or System.DllNotFoundException)) { + ( ex is System.TypeInitializationException tex && + tex.InnerException is System.PlatformNotSupportedException or System.DllNotFoundException )) { // 在Linux上GDI+不可用时跳过测试(包括libgdiplus未安装或平台不支持) System.Console.WriteLine($"跳过测试:{ex.Message}"); return; diff --git a/TelegramSearchBot/Controller/AI/LLM/LLMIterationCallbackController.cs b/TelegramSearchBot/Controller/AI/LLM/LLMIterationCallbackController.cs index 22f6d48c..e51db817 100644 --- a/TelegramSearchBot/Controller/AI/LLM/LLMIterationCallbackController.cs +++ b/TelegramSearchBot/Controller/AI/LLM/LLMIterationCallbackController.cs @@ -154,7 +154,7 @@ await _botClient.EditMessageReplyMarkup( List sentMessagesForDb = await _sendMessageService.SendDraftStream( resumeStream, snapshot.ChatId, - (int)snapshot.OriginalMessageId, + ( int ) snapshot.OriginalMessageId, initialContent, CancellationToken.None ); @@ -198,7 +198,7 @@ await _botClient.SendMessage( snapshot.ChatId, $"⚠️ AI 再次达到最大迭代次数限制({Env.MaxToolCycles} 次),已完成 {executionContext.SnapshotData.CyclesSoFar} 次循环,是否继续迭代?", replyMarkup: keyboard, - replyParameters: new ReplyParameters { MessageId = (int)snapshot.OriginalMessageId } + replyParameters: new ReplyParameters { MessageId = ( int ) snapshot.OriginalMessageId } ); } } finally { diff --git a/TelegramSearchBot/Interface/Tools/ISendDocumentToolService.cs b/TelegramSearchBot/Interface/Tools/ISendDocumentToolService.cs index 5c8dde24..59eda260 100644 --- a/TelegramSearchBot/Interface/Tools/ISendDocumentToolService.cs +++ b/TelegramSearchBot/Interface/Tools/ISendDocumentToolService.cs @@ -8,6 +8,6 @@ Task SendDocumentFile( string filePath, ToolContext toolContext, string caption = null, - int? replyToMessageId = null); + long? replyToMessageId = null); } } diff --git a/TelegramSearchBot/Interface/Tools/ISendVideoToolService.cs b/TelegramSearchBot/Interface/Tools/ISendVideoToolService.cs index 8d275d9c..404ca12e 100644 --- a/TelegramSearchBot/Interface/Tools/ISendVideoToolService.cs +++ b/TelegramSearchBot/Interface/Tools/ISendVideoToolService.cs @@ -8,6 +8,6 @@ Task SendVideoFile( string filePath, ToolContext toolContext, string caption = null, - int? replyToMessageId = null); + long? replyToMessageId = null); } } diff --git a/TelegramSearchBot/Service/BotAPI/SendMessageService.Streaming.cs b/TelegramSearchBot/Service/BotAPI/SendMessageService.Streaming.cs index 605949f1..f32db4df 100644 --- a/TelegramSearchBot/Service/BotAPI/SendMessageService.Streaming.cs +++ b/TelegramSearchBot/Service/BotAPI/SendMessageService.Streaming.cs @@ -359,7 +359,7 @@ await botClient.SendMessage( } // Generate a unique draftId to avoid collisions for concurrent requests to the same message - int draftId = unchecked((int)(chatId ^ replyTo ^ DateTime.UtcNow.Ticks)); + int draftId = unchecked(( int ) ( chatId ^ replyTo ^ DateTime.UtcNow.Ticks )); string latestContent = null; bool draftStarted = false; diff --git a/TelegramSearchBot/Service/Storage/MessageService.cs b/TelegramSearchBot/Service/Storage/MessageService.cs index 39051747..2fc829a9 100644 --- a/TelegramSearchBot/Service/Storage/MessageService.cs +++ b/TelegramSearchBot/Service/Storage/MessageService.cs @@ -54,7 +54,7 @@ public async Task AddToSqlite(MessageOption messageOption) { var existingUserInGroup = await DataContext.UsersWithGroup .FirstOrDefaultAsync(s => s.UserId == messageOption.UserId && s.GroupId == messageOption.ChatId); - + if (existingUserInGroup == null) { // 使用 try-catch 处理并发插入导致的唯一约束冲突 try { @@ -75,7 +75,7 @@ await DataContext.UsersWithGroup.AddAsync(new UserWithGroup() { var existingUserData = await DataContext.UserData .FirstOrDefaultAsync(s => s.Id == messageOption.User.Id); - + if (existingUserData == null && messageOption.User != null) { try { await DataContext.UserData.AddAsync(new UserData() { @@ -96,7 +96,7 @@ await DataContext.UserData.AddAsync(new UserData() { var existingGroupData = await DataContext.GroupData .FirstOrDefaultAsync(s => s.Id == messageOption.Chat.Id); - + if (existingGroupData == null && messageOption.Chat != null) { try { await DataContext.GroupData.AddAsync(new GroupData() { @@ -112,7 +112,7 @@ await DataContext.GroupData.AddAsync(new GroupData() { DataContext.ChangeTracker.Clear(); } } - + var message = new Message() { GroupId = messageOption.ChatId, MessageId = messageOption.MessageId, @@ -123,7 +123,7 @@ await DataContext.GroupData.AddAsync(new GroupData() { if (messageOption.ReplyTo != 0) { message.ReplyToMessageId = messageOption.ReplyTo; } - + await DataContext.Messages.AddAsync(message); await DataContext.SaveChangesAsync(); return message.Id; diff --git a/TelegramSearchBot/Service/Tools/SendDocumentToolService.cs b/TelegramSearchBot/Service/Tools/SendDocumentToolService.cs index b22a522f..b8e69d02 100644 --- a/TelegramSearchBot/Service/Tools/SendDocumentToolService.cs +++ b/TelegramSearchBot/Service/Tools/SendDocumentToolService.cs @@ -31,9 +31,9 @@ public SendDocumentToolService(ITelegramBotClient botClient, SendMessage sendMes /// Resolves the effective reply-to message ID. Uses the explicitly provided value if set, /// otherwise falls back to the ToolContext.MessageId (the original user message). /// - private static ReplyParameters GetReplyParameters(int? explicitReplyToMessageId, ToolContext toolContext) { - long? messageId = explicitReplyToMessageId ?? (toolContext.MessageId != 0 ? toolContext.MessageId : (long?)null); - return messageId.HasValue ? new ReplyParameters { MessageId = (int)messageId.Value } : null; + private static ReplyParameters GetReplyParameters(long? explicitReplyToMessageId, ToolContext toolContext) { + long? messageId = explicitReplyToMessageId ?? ( toolContext.MessageId != 0 ? toolContext.MessageId : ( long? ) null ); + return messageId.HasValue ? new ReplyParameters { MessageId = ( int ) messageId.Value } : null; } [BuiltInTool("Sends a document/file to the current chat using a file path.", Name = "send_document_file")] @@ -41,7 +41,7 @@ public async Task SendDocumentFile( [BuiltInParameter("The file path to the document on the server.")] string filePath, ToolContext toolContext, [BuiltInParameter("Optional caption for the document (max 1024 characters).", IsRequired = false)] string caption = null, - [BuiltInParameter("Optional message ID to reply to.", IsRequired = false)] int? replyToMessageId = null) { + [BuiltInParameter("Optional message ID to reply to.", IsRequired = false)] long? replyToMessageId = null) { try { if (string.IsNullOrWhiteSpace(filePath)) { return new SendDocumentResult { diff --git a/TelegramSearchBot/Service/Tools/SendPhotoToolService.cs b/TelegramSearchBot/Service/Tools/SendPhotoToolService.cs index a91387e7..3a8f1a2c 100644 --- a/TelegramSearchBot/Service/Tools/SendPhotoToolService.cs +++ b/TelegramSearchBot/Service/Tools/SendPhotoToolService.cs @@ -31,9 +31,9 @@ public SendPhotoToolService(ITelegramBotClient botClient, SendMessage sendMessag /// Resolves the effective reply-to message ID. Uses the explicitly provided value if set, /// otherwise falls back to the ToolContext.MessageId (the original user message). /// - private static ReplyParameters GetReplyParameters(int? explicitReplyToMessageId, ToolContext toolContext) { - long? messageId = explicitReplyToMessageId ?? (toolContext.MessageId != 0 ? toolContext.MessageId : (long?)null); - return messageId.HasValue ? new ReplyParameters { MessageId = (int)messageId.Value } : null; + private static ReplyParameters GetReplyParameters(long? explicitReplyToMessageId, ToolContext toolContext) { + long? messageId = explicitReplyToMessageId ?? ( toolContext.MessageId != 0 ? toolContext.MessageId : ( long? ) null ); + return messageId.HasValue ? new ReplyParameters { MessageId = ( int ) messageId.Value } : null; } [BuiltInTool("Sends a photo to the current chat using base64 encoded image data.", Name = "send_photo_base64")] @@ -41,7 +41,7 @@ public async Task SendPhotoBase64( [BuiltInParameter("The base64 encoded image data (without the data URI prefix).")] string base64Data, ToolContext toolContext, [BuiltInParameter("Optional caption for the photo (max 1024 characters).", IsRequired = false)] string caption = null, - [BuiltInParameter("Optional message ID to reply to.", IsRequired = false)] int? replyToMessageId = null) { + [BuiltInParameter("Optional message ID to reply to.", IsRequired = false)] long? replyToMessageId = null) { try { byte[] imageBytes; try { @@ -94,7 +94,7 @@ public async Task SendPhotoFile( [BuiltInParameter("The file path to the image on the server.")] string filePath, ToolContext toolContext, [BuiltInParameter("Optional caption for the photo (max 1024 characters).", IsRequired = false)] string caption = null, - [BuiltInParameter("Optional message ID to reply to.", IsRequired = false)] int? replyToMessageId = null) { + [BuiltInParameter("Optional message ID to reply to.", IsRequired = false)] long? replyToMessageId = null) { try { if (string.IsNullOrWhiteSpace(filePath)) { return new SendPhotoResult { diff --git a/TelegramSearchBot/Service/Tools/SendVideoToolService.cs b/TelegramSearchBot/Service/Tools/SendVideoToolService.cs index 4e33ba8b..b3c007d9 100644 --- a/TelegramSearchBot/Service/Tools/SendVideoToolService.cs +++ b/TelegramSearchBot/Service/Tools/SendVideoToolService.cs @@ -31,9 +31,9 @@ public SendVideoToolService(ITelegramBotClient botClient, SendMessage sendMessag /// Resolves the effective reply-to message ID. Uses the explicitly provided value if set, /// otherwise falls back to the ToolContext.MessageId (the original user message). /// - private static ReplyParameters GetReplyParameters(int? explicitReplyToMessageId, ToolContext toolContext) { - long? messageId = explicitReplyToMessageId ?? (toolContext.MessageId != 0 ? toolContext.MessageId : (long?)null); - return messageId.HasValue ? new ReplyParameters { MessageId = (int)messageId.Value } : null; + private static ReplyParameters GetReplyParameters(long? explicitReplyToMessageId, ToolContext toolContext) { + long? messageId = explicitReplyToMessageId ?? ( toolContext.MessageId != 0 ? toolContext.MessageId : ( long? ) null ); + return messageId.HasValue ? new ReplyParameters { MessageId = ( int ) messageId.Value } : null; } [BuiltInTool("Sends a video to the current chat using a file path.", Name = "send_video_file")] @@ -41,7 +41,7 @@ public async Task SendVideoFile( [BuiltInParameter("The file path to the video on the server.")] string filePath, ToolContext toolContext, [BuiltInParameter("Optional caption for the video (max 1024 characters).", IsRequired = false)] string caption = null, - [BuiltInParameter("Optional message ID to reply to.", IsRequired = false)] int? replyToMessageId = null) { + [BuiltInParameter("Optional message ID to reply to.", IsRequired = false)] long? replyToMessageId = null) { try { if (string.IsNullOrWhiteSpace(filePath)) { return new SendVideoResult { diff --git a/TelegramSearchBot/Service/Vector/FaissVectorService.cs b/TelegramSearchBot/Service/Vector/FaissVectorService.cs index 24512ab0..035199af 100644 --- a/TelegramSearchBot/Service/Vector/FaissVectorService.cs +++ b/TelegramSearchBot/Service/Vector/FaissVectorService.cs @@ -75,7 +75,7 @@ private async Task CheckEmbeddingModelAvailabilityAsync() { try { // 尝试生成一个简单的测试向量 var testVector = await _generalLLMService.GenerateEmbeddingsAsync("test", CancellationToken.None); - + if (testVector == null || testVector.Length == 0) { _logger.LogWarning("嵌入模型不可用或返回空向量,已禁用FAISS向量服务"); _isEnabled = false; @@ -680,14 +680,14 @@ private static void SetSegmentVectorizedStatus(DataDbContext dbContext, Conversa public async Task GenerateVectorAsync(string text) { try { var vector = await _generalLLMService.GenerateEmbeddingsAsync(text); - + // 如果返回空向量,记录警告并禁用服务 if (vector == null || vector.Length == 0) { _logger.LogWarning("嵌入模型返回空向量,FAISS向量服务已禁用"); _isEnabled = false; return Array.Empty(); } - + return vector; } catch (Exception ex) { _logger.LogWarning(ex, "生成向量时出错,FAISS向量服务已禁用"); From e0fc8ab9f60e2df30b0cfe635c81586cbcd1ead0 Mon Sep 17 00:00:00 2001 From: Test User Date: Fri, 20 Mar 2026 08:54:19 +0800 Subject: [PATCH 4/6] fix: resolve import ordering in ServiceCollectionExtension --- TelegramSearchBot/Extension/ServiceCollectionExtension.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TelegramSearchBot/Extension/ServiceCollectionExtension.cs b/TelegramSearchBot/Extension/ServiceCollectionExtension.cs index 409dedbd..f7135663 100644 --- a/TelegramSearchBot/Extension/ServiceCollectionExtension.cs +++ b/TelegramSearchBot/Extension/ServiceCollectionExtension.cs @@ -22,11 +22,11 @@ using TelegramSearchBot.Executor; using TelegramSearchBot.Helper; using TelegramSearchBot.Interface; +using TelegramSearchBot.Interface.AI.LLM; using TelegramSearchBot.Interface.Controller; using TelegramSearchBot.Manager; using TelegramSearchBot.Model; using TelegramSearchBot.Search.Tool; -using TelegramSearchBot.Interface.AI.LLM; using TelegramSearchBot.Service.BotAPI; using TelegramSearchBot.Service.Storage; using TelegramSearchBot.View; From 858dd15ae62556369ae8b5b1dc8775a3b229ab7c Mon Sep 17 00:00:00 2001 From: Test User Date: Mon, 23 Mar 2026 15:48:44 +0800 Subject: [PATCH 5/6] feat: use dynamic file size limit based on IsLocalAPI (2GB if local, 50MB otherwise) --- TelegramSearchBot/Service/Tools/SendDocumentToolService.cs | 7 +++++-- TelegramSearchBot/Service/Tools/SendVideoToolService.cs | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/TelegramSearchBot/Service/Tools/SendDocumentToolService.cs b/TelegramSearchBot/Service/Tools/SendDocumentToolService.cs index b8e69d02..5fc60de8 100644 --- a/TelegramSearchBot/Service/Tools/SendDocumentToolService.cs +++ b/TelegramSearchBot/Service/Tools/SendDocumentToolService.cs @@ -6,6 +6,7 @@ using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; using TelegramSearchBot.Attributes; +using TelegramSearchBot.Common; using TelegramSearchBot.Interface; using TelegramSearchBot.Interface.Tools; using TelegramSearchBot.Manager; @@ -60,12 +61,14 @@ public async Task SendDocumentFile( } var fileInfo = new FileInfo(filePath); - const long maxFileSizeBytes = 2L * 1024 * 1024 * 1024; + long maxFileSizeBytes = Env.IsLocalAPI + ? 2L * 1024 * 1024 * 1024 // Local API: 2GB + : 50 * 1024 * 1024; // Bot API: 50MB if (fileInfo.Length > maxFileSizeBytes) { return new SendDocumentResult { Success = false, ChatId = toolContext.ChatId, - Error = $"File is too large ({fileInfo.Length / 1024 / 1024 / 1024}GB). Maximum allowed size is 2GB." + Error = $"File is too large ({fileInfo.Length / 1024 / 1024}MB). Maximum allowed size is {(Env.IsLocalAPI ? "2GB" : "50MB")}." }; } diff --git a/TelegramSearchBot/Service/Tools/SendVideoToolService.cs b/TelegramSearchBot/Service/Tools/SendVideoToolService.cs index b3c007d9..6bbd64aa 100644 --- a/TelegramSearchBot/Service/Tools/SendVideoToolService.cs +++ b/TelegramSearchBot/Service/Tools/SendVideoToolService.cs @@ -6,6 +6,7 @@ using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; using TelegramSearchBot.Attributes; +using TelegramSearchBot.Common; using TelegramSearchBot.Interface; using TelegramSearchBot.Interface.Tools; using TelegramSearchBot.Manager; @@ -60,12 +61,14 @@ public async Task SendVideoFile( } var fileInfo = new FileInfo(filePath); - const long maxFileSizeBytes = 50 * 1024 * 1024; // Telegram video limit: 50 MB + long maxFileSizeBytes = Env.IsLocalAPI + ? 2L * 1024 * 1024 * 1024 // Local API: 2GB + : 50 * 1024 * 1024; // Bot API: 50MB if (fileInfo.Length > maxFileSizeBytes) { return new SendVideoResult { Success = false, ChatId = toolContext.ChatId, - Error = $"File is too large ({fileInfo.Length / 1024 / 1024}MB). Maximum allowed size is 50MB." + Error = $"File is too large ({fileInfo.Length / 1024 / 1024}MB). Maximum allowed size is {(Env.IsLocalAPI ? "2GB" : "50MB")}." }; } From 7b3eb7a16d5b78782bb327374e9f79c2288ea4de Mon Sep 17 00:00:00 2001 From: Test User Date: Mon, 23 Mar 2026 15:52:51 +0800 Subject: [PATCH 6/6] style: fix formatting --- TelegramSearchBot/Service/Tools/SendDocumentToolService.cs | 2 +- TelegramSearchBot/Service/Tools/SendVideoToolService.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/TelegramSearchBot/Service/Tools/SendDocumentToolService.cs b/TelegramSearchBot/Service/Tools/SendDocumentToolService.cs index 5fc60de8..1e63d6f1 100644 --- a/TelegramSearchBot/Service/Tools/SendDocumentToolService.cs +++ b/TelegramSearchBot/Service/Tools/SendDocumentToolService.cs @@ -68,7 +68,7 @@ public async Task SendDocumentFile( return new SendDocumentResult { Success = false, ChatId = toolContext.ChatId, - Error = $"File is too large ({fileInfo.Length / 1024 / 1024}MB). Maximum allowed size is {(Env.IsLocalAPI ? "2GB" : "50MB")}." + Error = $"File is too large ({fileInfo.Length / 1024 / 1024}MB). Maximum allowed size is {( Env.IsLocalAPI ? "2GB" : "50MB" )}." }; } diff --git a/TelegramSearchBot/Service/Tools/SendVideoToolService.cs b/TelegramSearchBot/Service/Tools/SendVideoToolService.cs index 6bbd64aa..b5e1d373 100644 --- a/TelegramSearchBot/Service/Tools/SendVideoToolService.cs +++ b/TelegramSearchBot/Service/Tools/SendVideoToolService.cs @@ -68,7 +68,7 @@ public async Task SendVideoFile( return new SendVideoResult { Success = false, ChatId = toolContext.ChatId, - Error = $"File is too large ({fileInfo.Length / 1024 / 1024}MB). Maximum allowed size is {(Env.IsLocalAPI ? "2GB" : "50MB")}." + Error = $"File is too large ({fileInfo.Length / 1024 / 1024}MB). Maximum allowed size is {( Env.IsLocalAPI ? "2GB" : "50MB" )}." }; }