From 5541500ed22e3d20304c6722f52f17916362c7a9 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Wed, 18 Jun 2025 16:42:58 -0500 Subject: [PATCH] init tool calling --- .../Streams/RealtimeTextStream.cs} | 15 +++++-- .../Realtime/RealTimeCompletionProvider.cs | 7 +-- .../Providers/Chat/ChatCompletionProvider.cs | 44 ++++++++++++------- 3 files changed, 42 insertions(+), 24 deletions(-) rename src/{Plugins/BotSharp.Plugin.GoogleAI/Models/Realtime/RealtimeTranscriptionResponse.cs => Infrastructure/BotSharp.Core/Infrastructures/Streams/RealtimeTextStream.cs} (81%) diff --git a/src/Plugins/BotSharp.Plugin.GoogleAI/Models/Realtime/RealtimeTranscriptionResponse.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/Streams/RealtimeTextStream.cs similarity index 81% rename from src/Plugins/BotSharp.Plugin.GoogleAI/Models/Realtime/RealtimeTranscriptionResponse.cs rename to src/Infrastructure/BotSharp.Core/Infrastructures/Streams/RealtimeTextStream.cs index 0a383c80..e041a53b 100644 --- a/src/Plugins/BotSharp.Plugin.GoogleAI/Models/Realtime/RealtimeTranscriptionResponse.cs +++ b/src/Infrastructure/BotSharp.Core/Infrastructures/Streams/RealtimeTextStream.cs @@ -1,12 +1,12 @@ using System.IO; -namespace BotSharp.Plugin.GoogleAI.Models.Realtime; +namespace BotSharp.Core.Infrastructures.Streams; -internal class RealtimeTranscriptionResponse : IDisposable +public class RealtimeTextStream : IDisposable { - public RealtimeTranscriptionResponse() + public RealtimeTextStream() { - + } private bool _disposed = false; @@ -20,6 +20,13 @@ internal class RealtimeTranscriptionResponse : IDisposable } } + public long Length => _contentStream.Length; + + public bool IsNullOrEmpty() + { + return _contentStream == null || Length == 0; + } + public void Collect(string text) { if (_disposed) return; diff --git a/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Realtime/RealTimeCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Realtime/RealTimeCompletionProvider.cs index 2e95cfa2..f262efd7 100644 --- a/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Realtime/RealTimeCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Realtime/RealTimeCompletionProvider.cs @@ -1,11 +1,12 @@ -using System.Threading; using BotSharp.Abstraction.Hooks; using BotSharp.Abstraction.Realtime.Models.Session; +using BotSharp.Core.Infrastructures.Streams; using BotSharp.Core.Session; using BotSharp.Plugin.GoogleAI.Models.Realtime; using GenerativeAI; using GenerativeAI.Types; using GenerativeAI.Types.Converters; +using System.Threading; namespace BotSharp.Plugin.GoogleAi.Providers.Realtime; @@ -33,8 +34,8 @@ public class GoogleRealTimeProvider : IRealTimeCompletion UnknownTypeHandling = JsonUnknownTypeHandling.JsonElement }; - private RealtimeTranscriptionResponse _inputStream = new(); - private RealtimeTranscriptionResponse _outputStream = new(); + private RealtimeTextStream _inputStream = new(); + private RealtimeTextStream _outputStream = new(); private bool _isBlocking = false; private RealtimeHubConnection _conn; diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs index e3ea89fe..8643ebc2 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Chat/ChatCompletionProvider.cs @@ -1,7 +1,10 @@ using BotSharp.Abstraction.Hooks; +using BotSharp.Core.Infrastructures.Streams; using BotSharp.Core.Observables.Queues; +using EntityFrameworkCore.BootKit; using ModelContextProtocol.Protocol.Types; using OpenAI.Chat; +using static Microsoft.EntityFrameworkCore.DbLoggerCategory; namespace BotSharp.Plugin.OpenAI.Providers.Chat; @@ -188,9 +191,7 @@ public class ChatCompletionProvider : IChatCompletion var (prompt, messages, options) = PrepareOptions(agent, conversations); var hub = _services.GetRequiredService(); - var response = chatClient.CompleteChatStreamingAsync(messages, options); var messageId = conversations.LastOrDefault()?.MessageId ?? string.Empty; - var allText = string.Empty; hub.Push(new() { @@ -203,23 +204,30 @@ public class ChatCompletionProvider : IChatCompletion } }); - await foreach (var choice in response) + using var textStream = new RealtimeTextStream(); + var toolCalls = new List(); + + await foreach (var choice in chatClient.CompleteChatStreamingAsync(messages, options)) { + if (choice.ToolCallUpdates != null) + { + toolCalls.AddRange(choice.ToolCallUpdates); + } + if (choice.FinishReason == ChatFinishReason.FunctionCall || choice.FinishReason == ChatFinishReason.ToolCalls) { - var update = choice.ToolCallUpdates?.FirstOrDefault()?.FunctionArgumentsUpdate?.ToString() ?? string.Empty; - _logger.LogCritical($"Tool Call (reason: {choice.FinishReason}): {update}"); + var functionName = toolCalls.FirstOrDefault(x => !string.IsNullOrEmpty(x.FunctionName))?.FunctionName; + var args = toolCalls.Where(x => x.FunctionArgumentsUpdate != null).Select(x => x.FunctionArgumentsUpdate.ToString()).ToList(); + var functionArgument = string.Join(string.Empty, args); - //await onMessageReceived(new RoleDialogModel(AgentRole.Assistant, update) - //{ - // //RenderedInstruction = string.Join("\r\n", renderedInstructions) - //}); + _logger.LogCritical($"Tool Call: {functionName}({functionArgument})"); } else if (!choice.ContentUpdate.IsNullOrEmpty()) { var text = choice.ContentUpdate[0]?.Text ?? string.Empty; - allText += text; - _logger.LogCritical($"Content update (reason: {choice.FinishReason}) {text}"); + textStream.Collect(text); + + _logger.LogCritical($"Content update: {text}"); var content = new RoleDialogModel(AgentRole.Assistant, text) { @@ -232,11 +240,6 @@ public class ChatCompletionProvider : IChatCompletion EventName = "OnReceiveLlmStreamMessage", Data = content }); - - //await onMessageReceived(new RoleDialogModel(choice.Role?.ToString() ?? ChatMessageRole.Assistant.ToString(), choice.ContentUpdate[0]?.Text ?? string.Empty) - //{ - // RenderedInstruction = string.Join("\r\n", renderedInstructions) - //}); } } @@ -244,7 +247,7 @@ public class ChatCompletionProvider : IChatCompletion { ServiceProvider = _services, EventName = "AfterReceiveLlmStreamMessage", - Data = new RoleDialogModel(AgentRole.Assistant, allText) + Data = new RoleDialogModel(AgentRole.Assistant, textStream.GetText()) { CurrentAgentId = agent.Id, MessageId = messageId @@ -452,4 +455,11 @@ public class ChatCompletionProvider : IChatCompletion { _model = model; } +} + + +class ToolCallData +{ + public ChatFinishReason? Reason { get; set; } + public List ToolCalls { get; set; } = []; } \ No newline at end of file