Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using BotSharp.Abstraction.Functions.Models;

namespace BotSharp.Abstraction.Loggers;

/// <summary>
Expand Down Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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!;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ public void StartTimer()

public void StopTimer()
{
if (_timer == null)
{
return;
}
_timer.Stop();
}
}
20 changes: 10 additions & 10 deletions src/Infrastructure/BotSharp.Core/Realtime/RealtimeHub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,11 @@ private async Task ConnectToModel(IRealTimeCompletion completer, WebSocket userW

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)
{
Expand Down Expand Up @@ -125,21 +127,19 @@ await completer.Connect(conn,
{
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
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public async Task<bool> InvokeAgent(string agentId, List<RoleDialogModel> dialog
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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
27 changes: 27 additions & 0 deletions src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,33 @@ public override async Task OnPostbackMessageReceived(RoleDialogModel message, Po
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

<ItemGroup>
<ProjectReference Include="..\..\Infrastructure\BotSharp.Abstraction\BotSharp.Abstraction.csproj" />
<ProjectReference Include="..\..\Infrastructure\BotSharp.Core\BotSharp.Core.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -212,14 +214,9 @@ public async Task SendEventToModel(object message)

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);
Expand Down Expand Up @@ -265,13 +262,23 @@ public async Task UpdateSession(RealtimeHubConnection conn)
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
{
Expand All @@ -287,21 +294,17 @@ public async Task UpdateSession(RealtimeHubConnection conn)
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);
}

Expand Down Expand Up @@ -568,7 +571,7 @@ public async Task<List<RoleDialogModel>> OnResponsedDone(RealtimeHubConnection c
{
outputs.Add(new RoleDialogModel(output.Role, output.Arguments)
{
CurrentAgentId = conn.EntryAgentId,
CurrentAgentId = conn.CurrentAgentId,
FunctionName = output.Name,
FunctionArgs = output.Arguments,
ToolCallId = output.CallId,
Expand All @@ -581,11 +584,27 @@ public async Task<List<RoleDialogModel>> OnResponsedDone(RealtimeHubConnection c

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

Expand All @@ -594,7 +613,7 @@ public async Task<RoleDialogModel> OnInputAudioTranscriptionCompleted(RealtimeHu
var data = JsonSerializer.Deserialize<ResponseAudioTranscript>(response);
return new RoleDialogModel(AgentRole.User, data.Transcript)
{
CurrentAgentId = conn.EntryAgentId
CurrentAgentId = conn.CurrentAgentId
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Net.Mime;
using System.Text.Json;
using System.Text;
using BotSharp.Abstraction.Options;

namespace BotSharp.Plugin.OpenAI.Providers.Text;

Expand All @@ -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(
Expand Down Expand Up @@ -110,7 +103,7 @@ private async Task<TextCompletionResponse> GetTextCompletion(string apiUrl, stri
MaxTokens = maxTokens,
Temperature = temperature
};
var data = JsonSerializer.Serialize(request, _jsonOptions);
var data = JsonSerializer.Serialize(request, BotSharpOptions.defaultJsonOptions);
var httpRequest = new HttpRequestMessage
{
Method = HttpMethod.Post,
Expand All @@ -121,7 +114,7 @@ private async Task<TextCompletionResponse> GetTextCompletion(string apiUrl, stri
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)
Expand Down
Loading