Skip to content

refine indication #1114

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Aug 6, 2025
Merged

Conversation

iceljc
Copy link
Collaborator

@iceljc iceljc commented Jul 28, 2025

PR Type

Enhancement


Description

  • Refactor event system with centralized EventEmitter and observer pattern

  • Replace IConversationProgressService with message hub observers

  • Add function indication support with real-time streaming

  • Enhance SideCar state inheritance with accumulation logic


Diagram Walkthrough

flowchart LR
  A["Old Event System"] --> B["EventEmitter Helper"]
  C["IConversationProgressService"] --> D["MessageHub Observers"]
  E["Function Execution"] --> F["Indication Support"]
  G["SideCar States"] --> H["Enhanced Inheritance"]
Loading

File Walkthrough

Relevant files
Enhancement
19 files
StreamingLogHook.cs
Replace direct SignalR calls with EventEmitter                     
+47/-116
ChatHubConversationHook.cs
Refactor to use centralized event emission                             
+20/-148
ChatHubObserver.cs
Add indication support and observer pattern                           
+100/-123
ConversationController.cs
Replace progress service with observer pattern                     
+20/-16 
BotSharpConversationSideCar.cs
Add LLM stats accumulation logic                                                 
+57/-4   
RoutingService.InvokeFunction.cs
Add indication support via message hub                                     
+16/-12 
RoutingService.InvokeAgent.cs
Update to use InvokeAgentOptions parameter                             
+9/-8     
ChatCompletionProvider.cs
Update message hub event names                                                     
+11/-10 
WelcomeHook.cs
Migrate to EventEmitter pattern                                                   
+10/-26 
ObserverService.cs
Add observer subscription management service                         
+86/-0   
ChatHubCrontabHook.cs
Simplify event emission for crontab                                           
+8/-10   
ChatEvent.cs
Define centralized chat event constants                                   
+22/-0   
ObserverSubscriptionContainer.cs
Add observer subscription container                                           
+64/-0   
GetFunEventsFn.cs
Add demo function with indication support                               
+51/-0   
EventEmitter.cs
Create centralized event emission helper                                 
+39/-0   
BotSharpObserverBase.cs
Add base observer implementation                                                 
+48/-0   
InvokeOptions.cs
Add typed options for function invocation                               
+31/-0   
IConversationHook.cs
Update hook interface with options parameter                         
+2/-2     
MessageHub.cs
Add thread-safe message hub implementation                             
+3/-3     
Configuration changes
1 files
ChatHubPlugin.cs
Register ChatHubObserver as service                                           
+7/-11   
Additional files
39 files
ConversationHookBase.cs +2/-2     
ChatResponseDto.cs +4/-0     
IConversationProgressService.cs +0/-10   
ConversationSenderActionModel.cs +0/-2     
RoleDialogModel.cs +1/-1     
HubObserveData.cs +6/-0     
ObserveDataBase.cs +7/-0     
ObserverSubscription.cs +28/-0   
IBotSharpObserver.cs +11/-0   
IObserverService.cs +13/-0   
HubObserveData.cs +0/-7     
ObserveDataBase.cs +0/-6     
IRoutingService.cs +2/-2     
SideCarAttribute.cs +6/-1     
SideCarOptions.cs +7/-3     
RealtimeConversationHook.cs +3/-3     
RealtimeHub.cs +1/-1     
BotSharp.Core.csproj +4/-1     
ConversationPlugin.cs +12/-4   
ConversationProgressService.cs +0/-11   
GetWeatherFn.cs +52/-0   
EvaluationConversationHook.cs +3/-2     
ConversationObserver.cs +45/-0   
FileRepository.Conversation.cs +2/-7     
FileRepository.KnowledgeBase.cs +5/-5     
InstructExecutor.cs +8/-6     
RoutingService.cs +6/-5     
Using.cs +3/-0     
get_fun_events.json +15/-0   
get_weather.json +1/-0     
RealtimeController.cs +1/-1     
Using.cs +2/-1     
ChatCompletionProvider.cs +11/-9   
KnowledgeService.Vector.cs +20/-21 
ChatCompletionProvider.cs +11/-9   
ChatCompletionProvider.cs +10/-12 
ChatCompletionProvider.cs +12/-10 
TwilioConversationHook.cs +2/-1     
TwilioMessageQueueService.cs +12/-10 

@iceljc iceljc marked this pull request as draft July 28, 2025 22:45
Copy link

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Hardcoded Delays

The function contains hardcoded Task.Delay calls (1000ms, 1500ms, 1500ms) which will block execution and may impact performance in production. Consider making these configurable or removing them entirely.

await Task.Delay(1000);

message.Indication = "Start querying weather data";
messageHub.Push(new()
{
    EventName = ChatEvent.OnIndicationReceived,
    Data = message,
    ServiceProvider = _services
});

await Task.Delay(1500);

message.Indication = "Still working on it";
messageHub.Push(new()
{
    EventName = ChatEvent.OnIndicationReceived,
    Data = message,
    ServiceProvider = _services
});

await Task.Delay(1500);
Blocking Async Call

The code uses ConfigureAwait(false).GetAwaiter().GetResult() which can cause deadlocks in certain contexts. This synchronous blocking of async operations should be avoided.

    chatHub.Clients.Group(conversationId).SendAsync(ChatEvent.OnSenderActionGenerated, action).ConfigureAwait(false).GetAwaiter().GetResult();
}
else
{
    var user = _services.GetRequiredService<IUserIdentity>();
    chatHub.Clients.User(user.Id).SendAsync(ChatEvent.OnSenderActionGenerated, action).ConfigureAwait(false).GetAwaiter().GetResult();
Thread Safety

The MessageHub now uses Subject.Synchronize() for thread safety, but this change should be validated to ensure it doesn't introduce performance bottlenecks or unexpected behavior in high-concurrency scenarios.

private readonly ISubject<T> _observable = Subject.Synchronize(new Subject<T>());
public IObservable<T> Events => _observable;

Copy link

qodo-merge-pro bot commented Jul 28, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix potential deadlock with async method

Using GetAwaiter().GetResult() can cause deadlocks in ASP.NET contexts and
blocks the thread unnecessarily. This should be made properly asynchronous or
use Task.Run if synchronous execution is required.

src/Plugins/BotSharp.Plugin.ChatHub/Observers/ChatHubObserver.cs [160]

-chatHub.Clients.Group(conversationId).SendAsync(ChatEvent.OnSenderActionGenerated, action).ConfigureAwait(false).GetAwaiter().GetResult();
+await chatHub.Clients.Group(conversationId).SendAsync(ChatEvent.OnSenderActionGenerated, action);
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies that using .GetAwaiter().GetResult() is a "sync-over-async" anti-pattern that can lead to deadlocks and thread pool starvation, which is a critical issue in a server application.

Medium
General
Remove hard-coded demonstration delays

Hard-coded delays in production code can negatively impact user experience and
system performance. Consider making these delays configurable or removing them
entirely if they're only for demonstration purposes.

src/Infrastructure/BotSharp.Core/Demo/Functions/GetWeatherFn.cs [23-43]

-await Task.Delay(1000);
-...
-await Task.Delay(1500);
-...
-await Task.Delay(1500);
+// Remove hard-coded delays or make them configurable
+// await Task.Delay(_settings.IndicationDelay);

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 5

__

Why: The suggestion correctly points out that hard-coded Task.Delay calls are inappropriate for production code, as they are likely placeholders for demonstration and can degrade user experience.

Low
  • More

@iceljc iceljc marked this pull request as ready for review August 6, 2025 14:58
@iceljc iceljc merged commit f89648e into SciSharp:master Aug 6, 2025
4 checks passed
Copy link

qodo-merge-pro bot commented Aug 6, 2025

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Possible Issue

The AccumulateLlmStats method uses ParseNumber with generic type constraints but the switch statement may not handle all numeric types correctly. The float parsing logic could fail for certain culture-specific decimal separators.

private T ParseNumber<T>(string? data) where T : struct
{
    if (string.IsNullOrEmpty(data))
    {
        return default;
    }

    return typeof(T) switch
    {
        Type t when t == typeof(int) => (T)(object)(int.TryParse(data, out var i) ? i : 0),
        Type t when t == typeof(float) => (T)(object)(float.TryParse(data, out var f) ? f : 0),
        _ => default
    };
}
Performance Issue

The SendEvent method uses ConfigureAwait(false).GetAwaiter().GetResult() which can cause deadlocks in certain contexts and blocks the calling thread. This synchronous-over-async pattern should be avoided.

EventEmitter.SendChatEvent(_services, _logger, @event, conversationId, user?.Id, data, nameof(ChatHubObserver), callerName)
             .ConfigureAwait(false).GetAwaiter().GetResult();
Logic Error

File.WriteAllText is called to create an empty JSON array but there's no validation that the file write operation succeeded before proceeding to read from it, which could cause JSON parsing errors.

    File.WriteAllText(breakpointFile, "[]");
}

Copy link

qodo-merge-pro bot commented Aug 6, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
General
Ensure directory exists before file creation

Creating a file and immediately reading from it can cause race conditions in
concurrent scenarios. Consider using a lock or checking if the directory exists
before file operations.

src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs [244-247]

 if (!File.Exists(breakpointFile))
 {
+    Directory.CreateDirectory(Path.GetDirectoryName(breakpointFile));
     File.WriteAllText(breakpointFile, "[]");
 }
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a potential DirectoryNotFoundException if the parent directory for breakpointFile does not exist, and provides a valid fix.

Medium
Improve null safety pattern

The null check should be performed before casting to avoid potential null
reference exceptions. Cast the instance first, then check if the cast result is
null before accessing ServiceProvider.

src/Infrastructure/BotSharp.Abstraction/SideCar/Attributes/SideCarAttribute.cs [26-30]

-var serviceProvider = (instance as IHaveServiceProvider)?.ServiceProvider;
-if (serviceProvider == null)
+if (!(instance is IHaveServiceProvider serviceProviderInstance))
 {
     return;
 }
+var serviceProvider = serviceProviderInstance.ServiceProvider;
  • Apply / Chat
Suggestion importance[1-10]: 2

__

Why: The suggestion's reasoning is incorrect as the existing code is already null-safe; however, the proposed change offers a minor stylistic improvement using a more modern C# pattern.

Low
Possible issue
Use type-safe conversion methods

The double casting pattern (T)(object) is unsafe and can throw
InvalidCastException at runtime. Use a more type-safe approach with generic
constraints or Convert methods.

src/Infrastructure/BotSharp.Core/SideCar/Services/BotSharpConversationSideCar.cs [267-280]

-private T ParseNumber<T>(string? data) where T : struct
+private T ParseNumber<T>(string? data) where T : struct, IConvertible
 {
     if (string.IsNullOrEmpty(data))
     {
         return default;
     }
 
-    return typeof(T) switch
+    try
     {
-        Type t when t == typeof(int) => (T)(object)(int.TryParse(data, out var i) ? i : 0),
-        Type t when t == typeof(float) => (T)(object)(float.TryParse(data, out var f) ? f : 0),
-        _ => default
-    };
+        return (T)Convert.ChangeType(data, typeof(T));
+    }
+    catch
+    {
+        return default;
+    }
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 6

__

Why: The suggestion correctly points out that the (T)(object) cast is unsafe and proposes a safer alternative using Convert.ChangeType, which improves type safety and robustness.

Low
  • More

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant