refine chat session
This commit is contained in:
parent
d05fa40bb7
commit
97f6dd7a44
|
|
@ -21,6 +21,7 @@
|
||||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageVersion Include="SharpHook" Version="5.3.9" />
|
<PackageVersion Include="SharpHook" Version="5.3.9" />
|
||||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.7" />
|
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.7" />
|
||||||
|
<PackageVersion Include="System.ClientModel" Version="1.3.0" />
|
||||||
<PackageVersion Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
<PackageVersion Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||||
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.0.0" />
|
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.0.0" />
|
||||||
<PackageVersion Include="System.Memory.Data" Version="8.0.0" />
|
<PackageVersion Include="System.Memory.Data" Version="8.0.0" />
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="NAudio" />
|
<PackageReference Include="NAudio" />
|
||||||
|
<PackageReference Include="System.ClientModel" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
namespace BotSharp.Plugin.OpenAI.Models.Realtime;
|
namespace BotSharp.Core.Realtime.Models.Chat;
|
||||||
|
|
||||||
public class ChatSessionUpdate
|
public class ChatSessionUpdate
|
||||||
{
|
{
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
using System.ClientModel.Primitives;
|
using System.ClientModel.Primitives;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.WebSockets;
|
|
||||||
|
|
||||||
namespace BotSharp.Plugin.OpenAI.Providers.Realtime.Session;
|
namespace BotSharp.Core.Realtime.Websocket.Chat;
|
||||||
|
|
||||||
internal class AiWebsocketPipelineResponse : PipelineResponse
|
public class AiWebsocketPipelineResponse : PipelineResponse
|
||||||
{
|
{
|
||||||
public AiWebsocketPipelineResponse()
|
public AiWebsocketPipelineResponse()
|
||||||
{
|
{
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
using System.ClientModel;
|
using System.ClientModel;
|
||||||
using System.Net.WebSockets;
|
|
||||||
|
|
||||||
namespace BotSharp.Plugin.OpenAI.Providers.Realtime.Session;
|
namespace BotSharp.Core.Realtime.Websocket.Chat;
|
||||||
|
|
||||||
internal class AsyncWebsocketDataCollectionResult : AsyncCollectionResult<ClientResult>
|
public class AsyncWebsocketDataCollectionResult : AsyncCollectionResult<ClientResult>
|
||||||
{
|
{
|
||||||
private readonly WebSocket _webSocket;
|
private readonly WebSocket _webSocket;
|
||||||
private readonly CancellationToken _cancellationToken;
|
private readonly CancellationToken _cancellationToken;
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
using System;
|
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.ClientModel;
|
using System.ClientModel;
|
||||||
using System.Net.WebSockets;
|
|
||||||
|
|
||||||
namespace BotSharp.Plugin.OpenAI.Providers.Realtime.Session;
|
namespace BotSharp.Core.Realtime.Websocket.Chat;
|
||||||
|
|
||||||
internal class AsyncWebsocketDataResultEnumerator : IAsyncEnumerator<ClientResult>
|
public class AsyncWebsocketDataResultEnumerator : IAsyncEnumerator<ClientResult>
|
||||||
{
|
{
|
||||||
private readonly WebSocket _webSocket;
|
private readonly WebSocket _webSocket;
|
||||||
private readonly CancellationToken _cancellationToken;
|
private readonly CancellationToken _cancellationToken;
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
using BotSharp.Plugin.OpenAI.Models.Realtime;
|
|
||||||
using System.ClientModel;
|
using System.ClientModel;
|
||||||
using System.Net.WebSockets;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using BotSharp.Core.Realtime.Models.Chat;
|
||||||
|
|
||||||
namespace BotSharp.Plugin.OpenAI.Providers.Realtime.Session;
|
namespace BotSharp.Core.Realtime.Websocket.Chat;
|
||||||
|
|
||||||
internal class RealtimeChatSession : IDisposable
|
public class RealtimeChatSession : IDisposable
|
||||||
{
|
{
|
||||||
private readonly IServiceProvider _services;
|
private readonly IServiceProvider _services;
|
||||||
private readonly BotSharpOptions _options;
|
private readonly JsonSerializerOptions _jsonOptions;
|
||||||
|
|
||||||
private ClientWebSocket _webSocket;
|
private ClientWebSocket _webSocket;
|
||||||
private readonly object _singleReceiveLock = new();
|
private readonly object _singleReceiveLock = new();
|
||||||
|
|
@ -17,23 +16,23 @@ internal class RealtimeChatSession : IDisposable
|
||||||
|
|
||||||
public RealtimeChatSession(
|
public RealtimeChatSession(
|
||||||
IServiceProvider services,
|
IServiceProvider services,
|
||||||
BotSharpOptions options)
|
JsonSerializerOptions jsonOptions)
|
||||||
{
|
{
|
||||||
_services = services;
|
_services = services;
|
||||||
_options = options;
|
_jsonOptions = jsonOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ConnectAsync(string provider, string model, CancellationToken cancellationToken = default)
|
public async Task ConnectAsync(Uri uri, Dictionary<string, string> headers, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var settingsService = _services.GetRequiredService<ILlmProviderService>();
|
|
||||||
var settings = settingsService.GetSetting(provider, model);
|
|
||||||
|
|
||||||
_webSocket?.Dispose();
|
_webSocket?.Dispose();
|
||||||
_webSocket = new ClientWebSocket();
|
_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={model}"), cancellationToken);
|
foreach (var header in headers)
|
||||||
|
{
|
||||||
|
_webSocket.Options.SetRequestHeader(header.Key, header.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _webSocket.ConnectAsync(uri, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async IAsyncEnumerable<ChatSessionUpdate> ReceiveUpdatesAsync([EnumeratorCancellation] CancellationToken cancellationToken = default)
|
public async IAsyncEnumerable<ChatSessionUpdate> ReceiveUpdatesAsync([EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||||
|
|
@ -82,7 +81,7 @@ internal class RealtimeChatSession : IDisposable
|
||||||
{
|
{
|
||||||
if (message is not string data)
|
if (message is not string data)
|
||||||
{
|
{
|
||||||
data = JsonSerializer.Serialize(message, _options.JsonSerializerOptions);
|
data = JsonSerializer.Serialize(message, _jsonOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
var buffer = Encoding.UTF8.GetBytes(data);
|
var buffer = Encoding.UTF8.GetBytes(data);
|
||||||
|
|
@ -15,8 +15,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Infrastructure\BotSharp.Abstraction\BotSharp.Abstraction.csproj" />
|
<ProjectReference Include="..\..\Infrastructure\BotSharp.Core.Realtime\BotSharp.Core.Realtime.csproj" />
|
||||||
<ProjectReference Include="..\..\Infrastructure\BotSharp.Core\BotSharp.Core.csproj" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
|
using BotSharp.Core.Realtime.Models.Chat;
|
||||||
|
using BotSharp.Core.Realtime.Websocket.Chat;
|
||||||
using BotSharp.Plugin.OpenAI.Models.Realtime;
|
using BotSharp.Plugin.OpenAI.Models.Realtime;
|
||||||
using BotSharp.Plugin.OpenAI.Providers.Realtime.Session;
|
|
||||||
using OpenAI.Chat;
|
using OpenAI.Chat;
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace BotSharp.Plugin.OpenAI.Providers.Realtime;
|
namespace BotSharp.Plugin.OpenAI.Providers.Realtime;
|
||||||
|
|
||||||
|
|
@ -44,15 +44,25 @@ public class RealTimeCompletionProvider : IRealTimeCompletion
|
||||||
Action<RoleDialogModel> onInputAudioTranscriptionCompleted,
|
Action<RoleDialogModel> onInputAudioTranscriptionCompleted,
|
||||||
Action onInterruptionDetected)
|
Action onInterruptionDetected)
|
||||||
{
|
{
|
||||||
|
var settingsService = _services.GetRequiredService<ILlmProviderService>();
|
||||||
var realtimeModelSettings = _services.GetRequiredService<RealtimeModelSettings>();
|
var realtimeModelSettings = _services.GetRequiredService<RealtimeModelSettings>();
|
||||||
|
|
||||||
_model = realtimeModelSettings.Model;
|
_model = realtimeModelSettings.Model;
|
||||||
|
var settings = settingsService.GetSetting(Provider, _model);
|
||||||
|
|
||||||
if (_session != null)
|
if (_session != null)
|
||||||
{
|
{
|
||||||
_session.Dispose();
|
_session.Dispose();
|
||||||
}
|
}
|
||||||
_session = new RealtimeChatSession(_services, _botsharpOptions);
|
_session = new RealtimeChatSession(_services, _botsharpOptions.JsonSerializerOptions);
|
||||||
await _session.ConnectAsync(Provider, _model, CancellationToken.None);
|
await _session.ConnectAsync(
|
||||||
|
new Uri($"wss://api.openai.com/v1/realtime?model={_model}"),
|
||||||
|
new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{"Authorization", $"Bearer {settings.ApiKey}"},
|
||||||
|
{"OpenAI-Beta", "realtime=v1"}
|
||||||
|
},
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
_ = ReceiveMessage(
|
_ = ReceiveMessage(
|
||||||
conn,
|
conn,
|
||||||
|
|
@ -600,6 +610,7 @@ public class RealTimeCompletionProvider : IRealTimeCompletion
|
||||||
|
|
||||||
var contentHooks = _services.GetServices<IContentGeneratingHook>().ToList();
|
var contentHooks = _services.GetServices<IContentGeneratingHook>().ToList();
|
||||||
|
|
||||||
|
var prompts = new List<string>();
|
||||||
var inputTokenDetails = data.Usage?.InputTokenDetails;
|
var inputTokenDetails = data.Usage?.InputTokenDetails;
|
||||||
var outputTokenDetails = data.Usage?.OutputTokenDetails;
|
var outputTokenDetails = data.Usage?.OutputTokenDetails;
|
||||||
|
|
||||||
|
|
@ -617,26 +628,7 @@ public class RealTimeCompletionProvider : IRealTimeCompletion
|
||||||
MessageType = MessageTypeName.FunctionCall
|
MessageType = MessageTypeName.FunctionCall
|
||||||
});
|
});
|
||||||
|
|
||||||
// After chat completion hook
|
prompts.Add($"{output.Name}({output.Arguments})");
|
||||||
foreach (var hook in contentHooks)
|
|
||||||
{
|
|
||||||
await hook.AfterGenerated(new RoleDialogModel(AgentRole.Assistant, $"{output.Name}\r\n{output.Arguments}")
|
|
||||||
{
|
|
||||||
CurrentAgentId = conn.CurrentAgentId
|
|
||||||
},
|
|
||||||
new TokenStatsModel
|
|
||||||
{
|
|
||||||
Provider = Provider,
|
|
||||||
Model = _model,
|
|
||||||
Prompt = $"{output.Name}\r\n{output.Arguments}",
|
|
||||||
TextInputTokens = inputTokenDetails?.TextTokens ?? 0 - inputTokenDetails?.CachedTokenDetails?.TextTokens ?? 0,
|
|
||||||
CachedTextInputTokens = data.Usage?.InputTokenDetails?.CachedTokenDetails?.TextTokens ?? 0,
|
|
||||||
AudioInputTokens = inputTokenDetails?.AudioTokens ?? 0 - inputTokenDetails?.CachedTokenDetails?.AudioTokens ?? 0,
|
|
||||||
CachedAudioInputTokens = inputTokenDetails?.CachedTokenDetails?.AudioTokens ?? 0,
|
|
||||||
TextOutputTokens = outputTokenDetails?.TextTokens ?? 0,
|
|
||||||
AudioOutputTokens = outputTokenDetails?.AudioTokens ?? 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (output.Type == "message")
|
else if (output.Type == "message")
|
||||||
{
|
{
|
||||||
|
|
@ -649,29 +641,32 @@ public class RealTimeCompletionProvider : IRealTimeCompletion
|
||||||
MessageType = MessageTypeName.Plain
|
MessageType = MessageTypeName.Plain
|
||||||
});
|
});
|
||||||
|
|
||||||
// After chat completion hook
|
prompts.Add(content.Transcript);
|
||||||
foreach (var hook in contentHooks)
|
|
||||||
{
|
|
||||||
await hook.AfterGenerated(new RoleDialogModel(AgentRole.Assistant, content.Transcript)
|
|
||||||
{
|
|
||||||
CurrentAgentId = conn.CurrentAgentId
|
|
||||||
},
|
|
||||||
new TokenStatsModel
|
|
||||||
{
|
|
||||||
Provider = Provider,
|
|
||||||
Model = _model,
|
|
||||||
Prompt = content.Transcript,
|
|
||||||
TextInputTokens = inputTokenDetails?.TextTokens ?? 0 - inputTokenDetails?.CachedTokenDetails?.TextTokens ?? 0,
|
|
||||||
CachedTextInputTokens = data.Usage?.InputTokenDetails?.CachedTokenDetails?.TextTokens ?? 0,
|
|
||||||
AudioInputTokens = inputTokenDetails?.AudioTokens ?? 0 - inputTokenDetails?.CachedTokenDetails?.AudioTokens ?? 0,
|
|
||||||
CachedAudioInputTokens = inputTokenDetails?.CachedTokenDetails?.AudioTokens ?? 0,
|
|
||||||
TextOutputTokens = outputTokenDetails?.TextTokens ?? 0,
|
|
||||||
AudioOutputTokens = outputTokenDetails?.AudioTokens ?? 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var text = string.Join("\r\n", prompts);
|
||||||
|
// After chat completion hook
|
||||||
|
foreach (var hook in contentHooks)
|
||||||
|
{
|
||||||
|
await hook.AfterGenerated(new RoleDialogModel(AgentRole.Assistant, text)
|
||||||
|
{
|
||||||
|
CurrentAgentId = conn.CurrentAgentId
|
||||||
|
},
|
||||||
|
new TokenStatsModel
|
||||||
|
{
|
||||||
|
Provider = Provider,
|
||||||
|
Model = _model,
|
||||||
|
Prompt = text,
|
||||||
|
TextInputTokens = inputTokenDetails?.TextTokens ?? 0 - inputTokenDetails?.CachedTokenDetails?.TextTokens ?? 0,
|
||||||
|
CachedTextInputTokens = data.Usage?.InputTokenDetails?.CachedTokenDetails?.TextTokens ?? 0,
|
||||||
|
AudioInputTokens = inputTokenDetails?.AudioTokens ?? 0 - inputTokenDetails?.CachedTokenDetails?.AudioTokens ?? 0,
|
||||||
|
CachedAudioInputTokens = inputTokenDetails?.CachedTokenDetails?.AudioTokens ?? 0,
|
||||||
|
TextOutputTokens = outputTokenDetails?.TextTokens ?? 0,
|
||||||
|
AudioOutputTokens = outputTokenDetails?.AudioTokens ?? 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return outputs;
|
return outputs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ await channel.ConnectAsync(conv.Id);
|
||||||
|
|
||||||
var hub = services.GetRequiredService<IRealtimeHub>();
|
var hub = services.GetRequiredService<IRealtimeHub>();
|
||||||
var conn = hub.SetHubConnection(conv.Id);
|
var conn = hub.SetHubConnection(conv.Id);
|
||||||
|
conn.CurrentAgentId = conv.AgentId;
|
||||||
|
|
||||||
conn.OnModelReady = () =>
|
conn.OnModelReady = () =>
|
||||||
JsonSerializer.Serialize(new
|
JsonSerializer.Serialize(new
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue