Merge branch 'master' of https://github.com/SciSharp/BotSharp
This commit is contained in:
commit
ac76a08524
|
|
@ -1,3 +1,5 @@
|
|||
using BotSharp.Abstraction.Functions.Models;
|
||||
|
||||
namespace BotSharp.Abstraction.Loggers;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -37,4 +39,13 @@ public interface IContentGeneratingHook
|
|||
/// <param name="content"></param>
|
||||
/// <returns></returns>
|
||||
Task OnRenderingTemplate(Agent agent, string name, string content) => Task.CompletedTask;
|
||||
|
||||
/// <summary>
|
||||
/// Realtime session updated
|
||||
/// </summary>
|
||||
/// <param name="agent"></param>
|
||||
/// <param name="instruction"></param>
|
||||
/// <param name="functions"></param>
|
||||
/// <returns></returns>
|
||||
Task OnSessionUpdated(Agent agent, string instruction, FunctionDef[] functions) => Task.CompletedTask;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ namespace BotSharp.Abstraction.Options;
|
|||
|
||||
public class BotSharpOptions
|
||||
{
|
||||
private readonly static JsonSerializerOptions defaultJsonOptions = new JsonSerializerOptions()
|
||||
public readonly static JsonSerializerOptions defaultJsonOptions = new JsonSerializerOptions()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ public class RealtimeHubConnection
|
|||
{
|
||||
public string Event { get; set; } = null!;
|
||||
public string StreamId { get; set; } = null!;
|
||||
public string EntryAgentId { get; set; } = null!;
|
||||
public string CurrentAgentId { get; set; } = null!;
|
||||
public string ConversationId { get; set; } = null!;
|
||||
public string Data { get; set; } = string.Empty;
|
||||
public string Model { get; set; } = null!;
|
||||
|
|
|
|||
|
|
@ -220,7 +220,6 @@ public class ConversationStateService : IConversationStateService
|
|||
{
|
||||
if (_conversationId == null || _sidecar?.IsEnabled() == true)
|
||||
{
|
||||
Reset();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -253,7 +252,6 @@ public class ConversationStateService : IConversationStateService
|
|||
}
|
||||
|
||||
_db.UpdateConversationStates(_conversationId, states);
|
||||
Reset();
|
||||
_logger.LogInformation($"Saved states of conversation {_conversationId}");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -113,6 +113,10 @@ public class TokenStatistics : ITokenStatistics
|
|||
|
||||
public void StopTimer()
|
||||
{
|
||||
if (_timer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_timer.Stop();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,9 +71,11 @@ public class RealtimeHub : IRealtimeHub
|
|||
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.LoadAgent(conversation.AgentId);
|
||||
conn.EntryAgentId = agent.Id;
|
||||
conn.CurrentAgentId = agent.Id;
|
||||
|
||||
var routing = _services.GetRequiredService<IRoutingService>();
|
||||
routing.Context.Push(agent.Id);
|
||||
|
||||
var dialogs = convService.GetDialogHistory();
|
||||
if (dialogs.Count == 0)
|
||||
{
|
||||
|
|
@ -125,21 +127,19 @@ public class RealtimeHub : IRealtimeHub
|
|||
{
|
||||
await routing.InvokeFunction(message.FunctionName, message);
|
||||
message.Role = AgentRole.Function;
|
||||
if (message.FunctionName == "route_to_agent")
|
||||
if (message.FunctionName == "route_to_agent" ||
|
||||
message.FunctionName == "util-routing-fallback_to_router")
|
||||
{
|
||||
var routedAgentId = routing.Context.GetCurrentAgentId();
|
||||
if (conn.EntryAgentId != routedAgentId)
|
||||
if (conn.CurrentAgentId != routedAgentId)
|
||||
{
|
||||
conn.EntryAgentId = routedAgentId;
|
||||
conn.CurrentAgentId = routedAgentId;
|
||||
await completer.UpdateSession(conn);
|
||||
await completer.TriggerModelInference("Reply based on the function's output.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await completer.InsertConversationItem(message);
|
||||
await completer.TriggerModelInference("Reply based on the function's output.");
|
||||
}
|
||||
|
||||
await completer.InsertConversationItem(message);
|
||||
await completer.TriggerModelInference("Reply based on the function's output.");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ public partial class RoutingService
|
|||
if (agent.Type == AgentType.Routing)
|
||||
{
|
||||
// Forgot about what situation needs to handle in this way
|
||||
// response.Content = "Apologies, I'm not quite sure I understand. Could you please provide additional clarification or context?";
|
||||
response.Content = "Apologies, I'm not quite sure I understand. Could you please provide additional clarification or context?";
|
||||
}
|
||||
|
||||
message = RoleDialogModel.From(message, role: AgentRole.Assistant, content: response.Content);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "util-routing-fallback_to_router",
|
||||
"description": "Get the appropriate agent who can handle the user request.",
|
||||
"description": "Return to the Router to find the appropriate agent who can handle the user's request.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
"If you're unsure whether you understand the user's request or if the user brings up an unrelated topic, call the function `util-routing-fallback_to_router` to get the appropriate agent from the router."
|
||||
Carefully consider whether the current user request is related to your responsibilities. Only when it is not relevant should you consider Return to the Router.
|
||||
|
|
@ -85,6 +85,33 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
await SendContentLog(conversationId, input);
|
||||
}
|
||||
|
||||
public async Task OnSessionUpdated(Agent agent, string instruction, FunctionDef[] functions)
|
||||
{
|
||||
var conversationId = _state.GetConversationId();
|
||||
if (string.IsNullOrEmpty(conversationId)) return;
|
||||
|
||||
// Agent queue log
|
||||
var log = $"{instruction}";
|
||||
if (functions.Length > 0)
|
||||
{
|
||||
log += $"\r\n\r\n[FUNCTIONS]:\r\n\r\n{string.Join("\r\n\r\n", functions.Select(x => JsonSerializer.Serialize(x, BotSharpOptions.defaultJsonOptions)))}";
|
||||
}
|
||||
_logger.LogInformation(log);
|
||||
|
||||
var message = new RoleDialogModel(AgentRole.Assistant, log)
|
||||
{
|
||||
MessageId = _routingCtx.MessageId
|
||||
};
|
||||
var input = new ContentLogInputModel(conversationId, message)
|
||||
{
|
||||
Name = agent.Name,
|
||||
AgentId = agent.Id,
|
||||
Source = ContentLogSource.Prompt,
|
||||
Log = log
|
||||
};
|
||||
await SendContentLog(conversationId, input);
|
||||
}
|
||||
|
||||
public async Task OnRenderingTemplate(Agent agent, string name, string content)
|
||||
{
|
||||
if (!_convSettings.ShowVerboseLog) return;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Infrastructure\BotSharp.Abstraction\BotSharp.Abstraction.csproj" />
|
||||
<ProjectReference Include="..\..\Infrastructure\BotSharp.Core\BotSharp.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
using BotSharp.Abstraction.Conversations.Enums;
|
||||
using BotSharp.Abstraction.Files.Utilities;
|
||||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Options;
|
||||
using BotSharp.Abstraction.Realtime.Models;
|
||||
using BotSharp.Core.Infrastructures;
|
||||
using BotSharp.Plugin.OpenAI.Models.Realtime;
|
||||
using OpenAI.Chat;
|
||||
using System.Net.WebSockets;
|
||||
|
|
@ -212,14 +214,9 @@ public class RealTimeCompletionProvider : IRealTimeCompletion
|
|||
|
||||
if (message is not string data)
|
||||
{
|
||||
data = JsonSerializer.Serialize(message, options: new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
});
|
||||
data = JsonSerializer.Serialize(message, BotSharpOptions.defaultJsonOptions);
|
||||
}
|
||||
|
||||
_logger.LogInformation($"SendEventToModel:\r\n{data}");
|
||||
|
||||
var buffer = Encoding.UTF8.GetBytes(data);
|
||||
|
||||
await _webSocket.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, CancellationToken.None);
|
||||
|
|
@ -265,13 +262,23 @@ public class RealTimeCompletionProvider : IRealTimeCompletion
|
|||
var conv = await convService.GetConversation(conn.ConversationId);
|
||||
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.LoadAgent(conn.EntryAgentId);
|
||||
var agent = await agentService.LoadAgent(conn.CurrentAgentId);
|
||||
|
||||
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;
|
||||
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();
|
||||
|
||||
var sessionUpdate = new
|
||||
{
|
||||
|
|
@ -287,21 +294,17 @@ public class RealTimeCompletionProvider : IRealTimeCompletion
|
|||
Voice = "alloy",
|
||||
Instructions = instruction,
|
||||
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(),
|
||||
Tools = functions,
|
||||
Modalities = [ "text", "audio" ],
|
||||
Temperature = Math.Max(options.Temperature ?? 0f, 0.6f)
|
||||
}
|
||||
};
|
||||
|
||||
await HookEmitter.Emit<IContentGeneratingHook>(_services, async hook =>
|
||||
{
|
||||
await hook.OnSessionUpdated(agent, instruction, functions);
|
||||
});
|
||||
|
||||
await SendEventToModel(sessionUpdate);
|
||||
}
|
||||
|
||||
|
|
@ -568,7 +571,7 @@ public class RealTimeCompletionProvider : IRealTimeCompletion
|
|||
{
|
||||
outputs.Add(new RoleDialogModel(output.Role, output.Arguments)
|
||||
{
|
||||
CurrentAgentId = conn.EntryAgentId,
|
||||
CurrentAgentId = conn.CurrentAgentId,
|
||||
FunctionName = output.Name,
|
||||
FunctionArgs = output.Arguments,
|
||||
ToolCallId = output.CallId,
|
||||
|
|
@ -581,11 +584,27 @@ public class RealTimeCompletionProvider : IRealTimeCompletion
|
|||
|
||||
outputs.Add(new RoleDialogModel(output.Role, content.Transcript)
|
||||
{
|
||||
CurrentAgentId = conn.EntryAgentId
|
||||
CurrentAgentId = conn.CurrentAgentId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
return outputs;
|
||||
}
|
||||
|
||||
|
|
@ -594,7 +613,7 @@ public class RealTimeCompletionProvider : IRealTimeCompletion
|
|||
var data = JsonSerializer.Deserialize<ResponseAudioTranscript>(response);
|
||||
return new RoleDialogModel(AgentRole.User, data.Transcript)
|
||||
{
|
||||
CurrentAgentId = conn.EntryAgentId
|
||||
CurrentAgentId = conn.CurrentAgentId
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using System.Net.Http;
|
|||
using System.Net.Mime;
|
||||
using System.Text.Json;
|
||||
using System.Text;
|
||||
using BotSharp.Abstraction.Options;
|
||||
|
||||
namespace BotSharp.Plugin.OpenAI.Providers.Text;
|
||||
|
||||
|
|
@ -13,14 +14,6 @@ public class TextCompletionProvider : ITextCompletion
|
|||
private readonly OpenAiSettings _settings;
|
||||
protected string _model;
|
||||
|
||||
protected readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
WriteIndented = true,
|
||||
AllowTrailingCommas = true,
|
||||
};
|
||||
|
||||
public virtual string Provider => "openai";
|
||||
|
||||
public TextCompletionProvider(
|
||||
|
|
@ -110,7 +103,7 @@ public class TextCompletionProvider : ITextCompletion
|
|||
MaxTokens = maxTokens,
|
||||
Temperature = temperature
|
||||
};
|
||||
var data = JsonSerializer.Serialize(request, _jsonOptions);
|
||||
var data = JsonSerializer.Serialize(request, BotSharpOptions.defaultJsonOptions);
|
||||
var httpRequest = new HttpRequestMessage
|
||||
{
|
||||
Method = HttpMethod.Post,
|
||||
|
|
@ -121,7 +114,7 @@ public class TextCompletionProvider : ITextCompletion
|
|||
var httpResponse = await httpClient.SendAsync(httpRequest);
|
||||
httpResponse.EnsureSuccessStatusCode();
|
||||
var responseStr = await httpResponse.Content.ReadAsStringAsync();
|
||||
var response = JsonSerializer.Deserialize<TextCompletionResponse>(responseStr, _jsonOptions);
|
||||
var response = JsonSerializer.Deserialize<TextCompletionResponse>(responseStr, BotSharpOptions.defaultJsonOptions);
|
||||
return response;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
|
|||
Loading…
Reference in a new issue