BotSharp/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs

704 lines
26 KiB
C#
Raw Normal View History

2025-02-26 17:41:52 +00:00
using BotSharp.Abstraction.Conversations.Enums;
2025-02-03 04:02:36 +00:00
using BotSharp.Abstraction.Files.Utilities;
using BotSharp.Abstraction.Functions.Models;
2025-02-28 18:44:59 +00:00
using BotSharp.Abstraction.Options;
2025-03-12 19:03:38 +00:00
using BotSharp.Abstraction.Realtime;
2025-02-03 04:02:36 +00:00
using BotSharp.Abstraction.Realtime.Models;
2025-03-12 19:03:38 +00:00
using BotSharp.Abstraction.Routing;
2025-02-28 18:44:59 +00:00
using BotSharp.Core.Infrastructures;
2025-02-07 17:18:47 +00:00
using BotSharp.Plugin.OpenAI.Models.Realtime;
2025-02-03 04:02:36 +00:00
using OpenAI.Chat;
2025-02-09 23:31:46 +00:00
using System.Net.WebSockets;
using System.Text;
2025-02-03 04:02:36 +00:00
using System.Text.Json;
2025-02-09 23:31:46 +00:00
using System.Threading;
2025-02-03 04:02:36 +00:00
namespace BotSharp.Plugin.OpenAI.Providers.Realtime;
2025-02-09 23:31:46 +00:00
/// <summary>
/// Reference to https://platform.openai.com/docs/api-reference/realtime-server-events
/// </summary>
2025-02-03 04:02:36 +00:00
public class RealTimeCompletionProvider : IRealTimeCompletion
{
public string Provider => "openai";
2025-02-07 22:40:57 +00:00
public string Model => _model;
2025-02-03 04:02:36 +00:00
protected readonly OpenAiSettings _settings;
protected readonly IServiceProvider _services;
protected readonly ILogger<RealTimeCompletionProvider> _logger;
protected string _model = "gpt-4o-mini-realtime-preview-2024-12-17";
2025-02-09 23:31:46 +00:00
private ClientWebSocket _webSocket;
2025-02-07 22:40:57 +00:00
2025-02-03 04:02:36 +00:00
public RealTimeCompletionProvider(
OpenAiSettings settings,
ILogger<RealTimeCompletionProvider> logger,
IServiceProvider services)
{
_settings = settings;
_logger = logger;
_services = services;
}
2025-02-09 23:31:46 +00:00
public async Task Connect(RealtimeHubConnection conn,
Action onModelReady,
2025-03-06 09:03:30 +00:00
Action<string,string> onModelAudioDeltaReceived,
2025-02-09 23:31:46 +00:00
Action onModelAudioResponseDone,
2025-03-12 19:03:38 +00:00
Action<string> onModelAudioTranscriptDone,
2025-02-11 23:27:07 +00:00
Action<List<RoleDialogModel>> onModelResponseDone,
Action<string> onConversationItemCreated,
Action<RoleDialogModel> onInputAudioTranscriptionCompleted,
2025-02-09 23:31:46 +00:00
Action onUserInterrupted)
{
var settingsService = _services.GetRequiredService<ILlmProviderService>();
var settings = settingsService.GetSetting(provider: "openai", conn.Model);
_webSocket = new ClientWebSocket();
_webSocket.Options.SetRequestHeader("Authorization", $"Bearer {settings.ApiKey}");
_webSocket.Options.SetRequestHeader("OpenAI-Beta", "realtime=v1");
await _webSocket.ConnectAsync(new Uri($"wss://api.openai.com/v1/realtime?model={conn.Model}"), CancellationToken.None);
if (_webSocket.State == WebSocketState.Open)
{
// Receive a message
2025-02-11 23:27:07 +00:00
_ = ReceiveMessage(conn,
2025-03-05 22:57:58 +00:00
onModelReady,
2025-02-11 23:27:07 +00:00
onModelAudioDeltaReceived,
2025-02-09 23:31:46 +00:00
onModelAudioResponseDone,
2025-03-12 19:03:38 +00:00
onModelAudioTranscriptDone,
2025-02-09 23:31:46 +00:00
onModelResponseDone,
2025-02-11 23:27:07 +00:00
onConversationItemCreated,
onInputAudioTranscriptionCompleted,
2025-02-09 23:31:46 +00:00
onUserInterrupted);
}
}
public async Task Disconnect()
{
2025-03-05 22:57:34 +00:00
if (_webSocket.State == WebSocketState.Open)
{
await _webSocket.CloseAsync(WebSocketCloseStatus.Empty, null, CancellationToken.None);
}
2025-02-09 23:31:46 +00:00
}
public async Task AppenAudioBuffer(string message)
{
var audioAppend = new
{
type = "input_audio_buffer.append",
audio = message
};
await SendEventToModel(audioAppend);
}
2025-02-10 21:52:10 +00:00
public async Task TriggerModelInference(string? instructions = null)
{
// Triggering model inference
2025-03-21 14:17:21 +00:00
if (!string.IsNullOrEmpty(instructions))
2025-02-10 21:52:10 +00:00
{
2025-03-21 14:17:21 +00:00
await SendEventToModel(new
2025-02-10 21:52:10 +00:00
{
2025-03-21 14:17:21 +00:00
type = "response.create",
response = new
{
instructions
}
});
}
else
{
await SendEventToModel(new
{
type = "response.create"
});
}
2025-02-10 21:52:10 +00:00
}
2025-03-01 00:19:44 +00:00
public async Task CancelModelResponse()
{
await SendEventToModel(new
{
type = "response.cancel"
});
}
public async Task RemoveConversationItem(string itemId)
{
await SendEventToModel(new
{
type = "conversation.item.delete",
item_id = itemId
});
}
2025-03-05 22:57:58 +00:00
private async Task ReceiveMessage(RealtimeHubConnection conn,
Action onModelReady,
2025-03-06 09:03:30 +00:00
Action<string,string> onModelAudioDeltaReceived,
2025-02-09 23:31:46 +00:00
Action onModelAudioResponseDone,
2025-03-12 19:03:38 +00:00
Action<string> onModelAudioTranscriptDone,
2025-02-11 23:27:07 +00:00
Action<List<RoleDialogModel>> onModelResponseDone,
Action<string> onConversationItemCreated,
2025-03-12 19:03:38 +00:00
Action<RoleDialogModel> onUserAudioTranscriptionCompleted,
2025-02-09 23:31:46 +00:00
Action onUserInterrupted)
{
2025-03-11 02:56:18 +00:00
var buffer = new byte[1024 * 32];
2025-02-09 23:31:46 +00:00
WebSocketReceiveResult result;
2025-03-03 21:30:06 +00:00
2025-02-09 23:31:46 +00:00
do
{
result = await _webSocket.ReceiveAsync(
new ArraySegment<byte>(buffer), CancellationToken.None);
2025-03-12 14:57:12 +00:00
2025-02-09 23:31:46 +00:00
// Convert received data to text/audio (Twilio sends Base64-encoded audio)
string receivedText = Encoding.UTF8.GetString(buffer, 0, result.Count);
if (string.IsNullOrEmpty(receivedText))
{
continue;
}
_logger.LogDebug($"{nameof(RealTimeCompletionProvider)} received: {receivedText}");
var response = JsonSerializer.Deserialize<ServerEventResponse>(receivedText);
if (response.Type == "error")
{
var error = JsonSerializer.Deserialize<ServerEventErrorResponse>(receivedText);
_logger.LogError($"Error: {error.Body.Message}");
}
else if (response.Type == "session.created")
{
2025-02-10 21:52:10 +00:00
_logger.LogInformation($"{response.Type}: {receivedText}");
2025-03-05 22:57:58 +00:00
onModelReady();
2025-02-09 23:31:46 +00:00
}
else if (response.Type == "session.updated")
{
2025-02-10 21:52:10 +00:00
_logger.LogInformation($"{response.Type}: {receivedText}");
2025-02-09 23:31:46 +00:00
}
else if (response.Type == "response.audio_transcript.delta")
{
}
else if (response.Type == "response.audio_transcript.done")
{
2025-02-10 21:52:10 +00:00
_logger.LogInformation($"{response.Type}: {receivedText}");
2025-02-09 23:31:46 +00:00
var data = JsonSerializer.Deserialize<ResponseAudioTranscript>(receivedText);
2025-03-03 21:30:06 +00:00
await Task.Delay(1000);
2025-03-12 19:03:38 +00:00
onModelAudioTranscriptDone(data.Transcript);
2025-02-09 23:31:46 +00:00
}
else if (response.Type == "response.audio.delta")
{
var audio = JsonSerializer.Deserialize<ResponseAudioDelta>(receivedText);
2025-03-06 09:03:30 +00:00
if (audio?.Delta != null)
2025-02-09 23:31:46 +00:00
{
2025-03-03 21:30:06 +00:00
_logger.LogDebug($"{response.Type}: {receivedText}");
2025-03-06 09:03:30 +00:00
onModelAudioDeltaReceived(audio.Delta, audio.ItemId);
2025-02-09 23:31:46 +00:00
}
}
else if (response.Type == "response.audio.done")
{
2025-02-10 21:52:10 +00:00
_logger.LogInformation($"{response.Type}: {receivedText}");
2025-02-09 23:31:46 +00:00
onModelAudioResponseDone();
}
else if (response.Type == "response.done")
{
2025-02-10 21:52:10 +00:00
_logger.LogInformation($"{response.Type}: {receivedText}");
2025-02-11 23:27:07 +00:00
var messages = await OnResponsedDone(conn, receivedText);
onModelResponseDone(messages);
}
else if (response.Type == "conversation.item.created")
{
_logger.LogInformation($"{response.Type}: {receivedText}");
onConversationItemCreated(receivedText);
}
else if (response.Type == "conversation.item.input_audio_transcription.completed")
{
_logger.LogInformation($"{response.Type}: {receivedText}");
2025-03-12 19:03:38 +00:00
var message = await OnUserAudioTranscriptionCompleted(conn, receivedText);
if (!string.IsNullOrEmpty(message.Content))
{
onUserAudioTranscriptionCompleted(message);
}
2025-02-09 23:31:46 +00:00
}
else if (response.Type == "input_audio_buffer.speech_started")
{
2025-03-05 18:29:28 +00:00
// Handle user interuption
2025-03-06 18:01:43 +00:00
if (conn.MarkQueue.Count > 0 && conn.ResponseStartTimestamp != null)
2025-02-09 23:31:46 +00:00
{
2025-03-06 18:01:43 +00:00
var elapsedTime = conn.LatestMediaTimestamp - conn.ResponseStartTimestamp;
2025-03-05 18:29:28 +00:00
2025-03-06 09:03:30 +00:00
if (!string.IsNullOrEmpty(conn.LastAssistantItemId))
2025-03-03 21:30:06 +00:00
{
2025-03-05 18:29:28 +00:00
var truncateEvent = new
{
type = "conversation.item.truncate",
2025-03-06 09:03:30 +00:00
item_id = conn.LastAssistantItemId,
2025-03-05 18:29:28 +00:00
content_index = 0,
audio_end_ms = elapsedTime
};
await SendEventToModel(truncateEvent);
}
2025-03-03 21:30:06 +00:00
onUserInterrupted();
}
2025-02-09 23:31:46 +00:00
}
} while (!result.CloseStatus.HasValue);
await _webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
}
public async Task SendEventToModel(object message)
{
2025-02-26 17:41:52 +00:00
if (_webSocket.State != WebSocketState.Open)
{
return;
}
2025-02-09 23:31:46 +00:00
if (message is not string data)
{
2025-02-28 18:44:59 +00:00
data = JsonSerializer.Serialize(message, BotSharpOptions.defaultJsonOptions);
2025-02-09 23:31:46 +00:00
}
var buffer = Encoding.UTF8.GetBytes(data);
2025-02-26 17:41:52 +00:00
2025-02-09 23:31:46 +00:00
await _webSocket.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, CancellationToken.None);
}
2025-02-03 04:02:36 +00:00
public async Task<RealtimeSession> CreateSession(Agent agent, List<RoleDialogModel> conversations)
{
var contentHooks = _services.GetServices<IContentGeneratingHook>().ToList();
var client = ProviderHelper.GetClient(Provider, _model, _services);
var chatClient = client.GetChatClient(_model);
var (prompt, messages, options) = PrepareOptions(agent, conversations);
2025-02-09 23:31:46 +00:00
var instruction = messages.FirstOrDefault()?.Content.FirstOrDefault()?.Text ?? agent.Description;
var args = new RealtimeSessionCreationRequest
2025-02-03 04:02:36 +00:00
{
2025-03-05 18:29:28 +00:00
Model = _model,
2025-02-09 23:31:46 +00:00
Instructions = instruction,
2025-02-03 04:02:36 +00:00
ToolChoice = "auto",
Tools = options.Tools.Select(x =>
{
var fn = new FunctionDef
{
Name = x.FunctionName,
Description = x.FunctionDescription
};
fn.Parameters = JsonSerializer.Deserialize<FunctionParametersDef>(x.FunctionParameters);
return fn;
}).ToArray(),
};
var settingsService = _services.GetRequiredService<ILlmProviderService>();
2025-03-05 18:29:28 +00:00
var settings = settingsService.GetSetting(Provider, args.Model ?? _model);
2025-02-03 04:02:36 +00:00
var api = _services.GetRequiredService<IOpenAiRealtimeApi>();
var session = await api.GetSessionAsync(args, settings.ApiKey);
return session;
}
2025-03-15 02:06:40 +00:00
public async Task<string> UpdateSession(RealtimeHubConnection conn, bool interruptResponse = true)
2025-02-09 23:31:46 +00:00
{
var convService = _services.GetRequiredService<IConversationService>();
var conv = await convService.GetConversation(conn.ConversationId);
var agentService = _services.GetRequiredService<IAgentService>();
2025-02-28 18:44:59 +00:00
var agent = await agentService.LoadAgent(conn.CurrentAgentId);
2025-02-09 23:31:46 +00:00
var client = ProviderHelper.GetClient(Provider, _model, _services);
var chatClient = client.GetChatClient(_model);
var (prompt, messages, options) = PrepareOptions(agent, []);
var instruction = messages.FirstOrDefault()?.Content.FirstOrDefault()?.Text ?? agent.Description;
2025-02-28 18:44:59 +00:00
var functions = options.Tools.Select(x =>
{
var fn = new FunctionDef
{
Name = x.FunctionName,
Description = x.FunctionDescription
};
fn.Parameters = JsonSerializer.Deserialize<FunctionParametersDef>(x.FunctionParameters);
return fn;
}).ToArray();
2025-02-09 23:31:46 +00:00
2025-03-12 19:03:38 +00:00
var words = new List<string>();
HookEmitter.Emit<IRealtimeHook>(_services, hook => words.AddRange(hook.OnModelTranscriptPrompt(agent)));
2025-03-20 22:54:49 +00:00
var realtimeModelSettings = _services.GetRequiredService<RealtimeModelSettings>();
2025-03-15 02:06:40 +00:00
2025-02-09 23:31:46 +00:00
var sessionUpdate = new
{
type = "session.update",
session = new RealtimeSessionUpdateRequest
{
InputAudioFormat = "g711_ulaw",
OutputAudioFormat = "g711_ulaw",
2025-02-11 23:27:07 +00:00
InputAudioTranscription = new InputAudioTranscription
{
2025-03-20 22:54:49 +00:00
Model = realtimeModelSettings.InputAudioTranscription.Model,
Language = realtimeModelSettings.InputAudioTranscription.Language,
2025-03-12 19:03:38 +00:00
Prompt = string.Join(", ", words.Select(x => x.ToLower().Trim()).Distinct()).SubstringMax(1024)
2025-02-11 23:27:07 +00:00
},
2025-03-20 22:54:49 +00:00
Voice = realtimeModelSettings.Voice,
2025-02-09 23:31:46 +00:00
Instructions = instruction,
ToolChoice = "auto",
2025-02-28 18:44:59 +00:00
Tools = functions,
2025-02-09 23:31:46 +00:00
Modalities = [ "text", "audio" ],
2025-03-20 22:54:49 +00:00
Temperature = Math.Max(options.Temperature ?? realtimeModelSettings.Temperature, 0.6f),
MaxResponseOutputTokens = realtimeModelSettings.MaxResponseOutputTokens,
2025-03-01 00:19:44 +00:00
TurnDetection = new RealtimeSessionTurnDetection
{
2025-03-22 00:28:22 +00:00
InterruptResponse = interruptResponse/*,
2025-03-20 22:54:49 +00:00
Threshold = realtimeModelSettings.TurnDetection.Threshold,
PrefixPadding = realtimeModelSettings.TurnDetection.PrefixPadding,
2025-03-22 00:28:22 +00:00
SilenceDuration = realtimeModelSettings.TurnDetection.SilenceDuration*/
2025-03-21 14:17:21 +00:00
},
InputAudioNoiseReduction = new InputAudioNoiseReduction
{
Type = "near_field"
2025-03-01 00:19:44 +00:00
}
2025-02-09 23:31:46 +00:00
}
};
2025-02-28 18:44:59 +00:00
await HookEmitter.Emit<IContentGeneratingHook>(_services, async hook =>
{
await hook.OnSessionUpdated(agent, instruction, functions);
});
2025-02-11 23:27:07 +00:00
await SendEventToModel(sessionUpdate);
return instruction;
2025-02-09 23:31:46 +00:00
}
2025-02-11 23:27:07 +00:00
public async Task InsertConversationItem(RoleDialogModel message)
2025-02-09 23:31:46 +00:00
{
2025-02-10 21:52:10 +00:00
if (message.Role == AgentRole.Function)
{
var functionConversationItem = new
{
type = "conversation.item.create",
item = new
{
call_id = message.ToolCallId,
type = "function_call_output",
output = message.Content
}
};
2025-02-11 23:27:07 +00:00
await SendEventToModel(functionConversationItem);
2025-02-10 21:52:10 +00:00
}
2025-02-11 23:27:07 +00:00
else if (message.Role == AgentRole.Assistant)
2025-02-09 23:31:46 +00:00
{
2025-02-10 23:28:03 +00:00
var conversationItem = new
2025-02-09 23:31:46 +00:00
{
2025-02-10 23:28:03 +00:00
type = "conversation.item.create",
item = new
2025-02-09 23:31:46 +00:00
{
2025-02-10 23:28:03 +00:00
type = "message",
role = message.Role,
content = new object[]
2025-02-09 23:31:46 +00:00
{
2025-02-10 23:28:03 +00:00
new
{
type = "text",
text = message.Content
}
2025-02-09 23:31:46 +00:00
}
}
2025-02-10 23:28:03 +00:00
};
2025-02-09 23:31:46 +00:00
2025-02-11 23:27:07 +00:00
await SendEventToModel(conversationItem);
}
else if (message.Role == AgentRole.User)
{
var conversationItem = new
{
type = "conversation.item.create",
item = new
{
type = "message",
role = message.Role,
content = new object[]
{
new
{
type = "input_text",
text = message.Content
}
}
}
};
await SendEventToModel(conversationItem);
2025-02-10 23:28:03 +00:00
}
else
{
throw new NotImplementedException("");
}
2025-02-09 23:31:46 +00:00
}
2025-02-03 04:02:36 +00:00
protected (string, IEnumerable<ChatMessage>, ChatCompletionOptions) PrepareOptions(Agent agent, List<RoleDialogModel> conversations)
{
var agentService = _services.GetRequiredService<IAgentService>();
var state = _services.GetRequiredService<IConversationStateService>();
var fileStorage = _services.GetRequiredService<IFileStorageService>();
var settingsService = _services.GetRequiredService<ILlmProviderService>();
var settings = settingsService.GetSetting(Provider, _model);
var allowMultiModal = settings != null && settings.MultiModal;
var messages = new List<ChatMessage>();
var temperature = float.Parse(state.GetState("temperature", "0.0"));
2025-02-05 23:50:32 +00:00
var maxTokens = int.TryParse(state.GetState("max_tokens"), out var tokens)
? tokens
: agent.LlmConfig?.MaxOutputTokens ?? LlmConstant.DEFAULT_MAX_OUTPUT_TOKEN;
2025-02-03 04:02:36 +00:00
var options = new ChatCompletionOptions()
{
ToolChoice = ChatToolChoice.CreateAutoChoice(),
Temperature = temperature,
MaxOutputTokenCount = maxTokens
};
var functions = agent.Functions.Concat(agent.SecondaryFunctions ?? []);
foreach (var function in functions)
{
if (!agentService.RenderFunction(agent, function)) continue;
var property = agentService.RenderFunctionProperty(agent, function);
options.Tools.Add(ChatTool.CreateFunctionTool(
functionName: function.Name,
functionDescription: function.Description,
functionParameters: BinaryData.FromObjectAsJson(property)));
}
if (!string.IsNullOrEmpty(agent.Instruction) || !agent.SecondaryInstructions.IsNullOrEmpty())
{
var text = agentService.RenderedInstruction(agent);
messages.Add(new SystemChatMessage(text));
}
if (!string.IsNullOrEmpty(agent.Knowledges))
{
messages.Add(new SystemChatMessage(agent.Knowledges));
}
var samples = ProviderHelper.GetChatSamples(agent.Samples);
foreach (var sample in samples)
{
messages.Add(sample.Role == AgentRole.User ? new UserChatMessage(sample.Content) : new AssistantChatMessage(sample.Content));
}
var filteredMessages = conversations.Select(x => x).ToList();
var firstUserMsgIdx = filteredMessages.FindIndex(x => x.Role == AgentRole.User);
if (firstUserMsgIdx > 0)
{
filteredMessages = filteredMessages.Where((_, idx) => idx >= firstUserMsgIdx).ToList();
}
foreach (var message in filteredMessages)
{
if (message.Role == AgentRole.Function)
{
messages.Add(new AssistantChatMessage(new List<ChatToolCall>
{
ChatToolCall.CreateFunctionToolCall(message.ToolCallId, message.FunctionName, BinaryData.FromString(message.FunctionArgs ?? string.Empty))
}));
messages.Add(new ToolChatMessage(message.ToolCallId, message.Content));
}
else if (message.Role == AgentRole.User)
{
var text = !string.IsNullOrWhiteSpace(message.Payload) ? message.Payload : message.Content;
var textPart = ChatMessageContentPart.CreateTextPart(text);
var contentParts = new List<ChatMessageContentPart> { textPart };
if (allowMultiModal && !message.Files.IsNullOrEmpty())
{
foreach (var file in message.Files)
{
if (!string.IsNullOrEmpty(file.FileData))
{
var (contentType, bytes) = FileUtility.GetFileInfoFromData(file.FileData);
var contentPart = ChatMessageContentPart.CreateImagePart(BinaryData.FromBytes(bytes), contentType, ChatImageDetailLevel.Auto);
contentParts.Add(contentPart);
}
else if (!string.IsNullOrEmpty(file.FileStorageUrl))
{
var contentType = FileUtility.GetFileContentType(file.FileStorageUrl);
var bytes = fileStorage.GetFileBytes(file.FileStorageUrl);
var contentPart = ChatMessageContentPart.CreateImagePart(BinaryData.FromBytes(bytes), contentType, ChatImageDetailLevel.Auto);
contentParts.Add(contentPart);
}
else if (!string.IsNullOrEmpty(file.FileUrl))
{
var uri = new Uri(file.FileUrl);
var contentPart = ChatMessageContentPart.CreateImagePart(uri, ChatImageDetailLevel.Auto);
contentParts.Add(contentPart);
}
}
}
messages.Add(new UserChatMessage(contentParts) { ParticipantName = message.FunctionName });
}
else if (message.Role == AgentRole.Assistant)
{
messages.Add(new AssistantChatMessage(message.Content));
}
}
var prompt = GetPrompt(messages, options);
return (prompt, messages, options);
}
private string GetPrompt(IEnumerable<ChatMessage> messages, ChatCompletionOptions options)
{
var prompt = string.Empty;
if (!messages.IsNullOrEmpty())
{
// System instruction
var verbose = string.Join("\r\n", messages
.Select(x => x as SystemChatMessage)
.Where(x => x != null)
.Select(x =>
{
if (!string.IsNullOrEmpty(x.ParticipantName))
{
// To display Agent name in log
return $"[{x.ParticipantName}]: {x.Content.FirstOrDefault()?.Text ?? string.Empty}";
}
return $"{AgentRole.System}: {x.Content.FirstOrDefault()?.Text ?? string.Empty}";
}));
prompt += $"{verbose}\r\n";
verbose = string.Join("\r\n", messages
.Where(x => x as SystemChatMessage == null)
.Select(x =>
{
var fnMessage = x as ToolChatMessage;
if (fnMessage != null)
{
return $"{AgentRole.Function}: {fnMessage.Content.FirstOrDefault()?.Text ?? string.Empty}";
}
var userMessage = x as UserChatMessage;
if (userMessage != null)
{
var content = x.Content.FirstOrDefault()?.Text ?? string.Empty;
return !string.IsNullOrEmpty(userMessage.ParticipantName) && userMessage.ParticipantName != "route_to_agent" ?
$"{userMessage.ParticipantName}: {content}" :
$"{AgentRole.User}: {content}";
}
var assistMessage = x as AssistantChatMessage;
if (assistMessage != null)
{
var toolCall = assistMessage.ToolCalls?.FirstOrDefault();
return toolCall != null ?
$"{AgentRole.Assistant}: Call function {toolCall?.FunctionName}({toolCall?.FunctionArguments})" :
$"{AgentRole.Assistant}: {assistMessage.Content.FirstOrDefault()?.Text ?? string.Empty}";
}
return string.Empty;
}));
2025-03-06 21:10:17 +00:00
if (!string.IsNullOrEmpty(verbose))
{
prompt += $"\r\n[CONVERSATION]\r\n{verbose}\r\n";
}
2025-02-03 04:02:36 +00:00
}
if (!options.Tools.IsNullOrEmpty())
{
var functions = string.Join("\r\n", options.Tools.Select(fn =>
{
return $"\r\n{fn.FunctionName}: {fn.FunctionDescription}\r\n{fn.FunctionParameters}";
}));
prompt += $"\r\n[FUNCTIONS]{functions}\r\n";
}
return prompt;
}
public void SetModelName(string model)
{
_model = model;
}
2025-02-09 23:31:46 +00:00
public async Task<List<RoleDialogModel>> OnResponsedDone(RealtimeHubConnection conn, string response)
{
var outputs = new List<RoleDialogModel>();
var data = JsonSerializer.Deserialize<ResponseDone>(response).Body;
2025-03-01 00:19:44 +00:00
if (data.Status != "completed")
{
return [];
}
2025-02-09 23:31:46 +00:00
foreach (var output in data.Outputs)
{
if (output.Type == "function_call")
{
2025-02-11 23:27:07 +00:00
outputs.Add(new RoleDialogModel(output.Role, output.Arguments)
2025-02-09 23:31:46 +00:00
{
2025-02-28 18:44:59 +00:00
CurrentAgentId = conn.CurrentAgentId,
2025-02-09 23:31:46 +00:00
FunctionName = output.Name,
2025-02-10 21:52:10 +00:00
FunctionArgs = output.Arguments,
2025-02-26 17:41:52 +00:00
ToolCallId = output.CallId,
2025-03-01 00:19:44 +00:00
MessageId = output.Id,
2025-02-26 17:41:52 +00:00
MessageType = MessageTypeName.FunctionCall
2025-02-09 23:31:46 +00:00
});
}
2025-02-11 23:27:07 +00:00
else if (output.Type == "message")
{
var content = output.Content.FirstOrDefault();
outputs.Add(new RoleDialogModel(output.Role, content.Transcript)
{
2025-03-03 21:30:06 +00:00
CurrentAgentId = conn.CurrentAgentId,
MessageId = output.Id,
MessageType = MessageTypeName.Plain
2025-02-11 23:27:07 +00:00
});
}
2025-02-09 23:31:46 +00:00
}
2025-02-28 18:44:59 +00:00
var contentHooks = _services.GetServices<IContentGeneratingHook>().ToList();
// After chat completion hook
foreach (var hook in contentHooks)
{
await hook.AfterGenerated(new RoleDialogModel(AgentRole.Assistant, "response.done")
{
CurrentAgentId = conn.CurrentAgentId
}, new TokenStatsModel
{
Provider = Provider,
Model = _model,
CompletionCount = data.Usage.OutputTokens,
PromptCount = data.Usage.InputTokens
});
}
2025-02-09 23:31:46 +00:00
return outputs;
}
2025-02-11 23:27:07 +00:00
2025-03-12 19:03:38 +00:00
public async Task<RoleDialogModel> OnUserAudioTranscriptionCompleted(RealtimeHubConnection conn, string response)
2025-02-11 23:27:07 +00:00
{
var data = JsonSerializer.Deserialize<ResponseAudioTranscript>(response);
return new RoleDialogModel(AgentRole.User, data.Transcript)
{
2025-02-28 18:44:59 +00:00
CurrentAgentId = conn.CurrentAgentId
2025-02-11 23:27:07 +00:00
};
}
public async Task<RoleDialogModel> OnConversationItemCreated(RealtimeHubConnection conn, string response)
{
var item = JsonSerializer.Deserialize<ConversationItemCreated>(response).Item;
var message = new RoleDialogModel(item.Role, item.Content.FirstOrDefault()?.Transcript);
return message;
}
2025-02-03 04:02:36 +00:00
}