Merge pull request #906 from hchen2020/master

OnSessionUpdated log
This commit is contained in:
Haiping 2025-02-28 12:49:57 -06:00 committed by GitHub
commit b5eade4c5e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 100 additions and 45 deletions

View file

@ -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;
}

View file

@ -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,

View file

@ -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!;

View file

@ -110,6 +110,10 @@ public class TokenStatistics : ITokenStatistics
public void StopTimer()
{
if (_timer == null)
{
return;
}
_timer.Stop();
}
}

View file

@ -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
{

View file

@ -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);

View file

@ -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": {

View file

@ -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.

View file

@ -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;

View file

@ -18,6 +18,7 @@
<ItemGroup>
<ProjectReference Include="..\..\Infrastructure\BotSharp.Abstraction\BotSharp.Abstraction.csproj" />
<ProjectReference Include="..\..\Infrastructure\BotSharp.Core\BotSharp.Core.csproj" />
</ItemGroup>
</Project>

View file

@ -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
};
}

View file

@ -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)