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
Expand Up @@ -43,6 +43,10 @@ public async Task<bool> SendMessage(string agentId,
message.Payload = replyMessage.Payload;
}

// Handle multi-language for input. Runs ahead of the hooks so they evaluate English content,
// and covers both routing paths from a single place.
await TranslateInboundMessage(agent, message);

var hooks = _services.GetHooksOrderByPriority<IConversationHook>(message.CurrentAgentId);
foreach (var hook in hooks)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,42 +1,45 @@
using BotSharp.Abstraction.Infrastructures.Enums;

namespace BotSharp.Core.Routing;
namespace BotSharp.Core.Conversations.Services;

public partial class RoutingService
public partial class ConversationService
{
private const string TranslationPromptName = "translation_prompt";

/// <summary>
/// Normalize an inbound user message to English so routing rules, agent instructions and
/// function arguments are always evaluated in English. The user's original text is kept in
/// SecondaryContent. Shared by InstructLoop and InstructDirect so every entry point into the
/// routing service behaves the same way.
/// Normalize an inbound user message to English so conversation hooks, routing rules, agent
/// instructions and function arguments are always evaluated in English. The user's original
/// text is kept in SecondaryContent. Runs once in SendMessage ahead of the hook loop, so both
/// routing paths and every hook observe the same normalized message.
/// </summary>
private async Task TranslateInboundMessage(Agent agent, RoleDialogModel message)
/// <returns>
/// True when the message was translated and SecondaryContent now holds the user's original text.
/// Callers rely on this to tell apart a SecondaryContent this method authored from one it never
/// touched, so it must stay false on every early return.
/// </returns>
private async Task<bool> TranslateInboundMessage(Agent agent, RoleDialogModel message)
{
var agentSettings = _services.GetRequiredService<AgentSettings>();
if (!agentSettings.EnableTranslator)
{
return;
return false;
}

var states = _services.GetRequiredService<IConversationStateService>();

// The caller supplies the language through the request states; the server does not detect it.
// TranslationService back-fills StateConst.LANGUAGE only when the state is absent, which cannot
// happen here - an absent state defaults to English and returns early. That back-fill serves
// the /translate endpoint instead.
// Unknown is excluded to stay in sync with TranslationResponseHook: it means the language has
// not been resolved, so translating would only paraphrase the user's own words.
var language = states.GetState(StateConst.LANGUAGE, LanguageType.ENGLISH);
var language = _state.GetState(StateConst.LANGUAGE, LanguageType.ENGLISH);
if (language == LanguageType.ENGLISH || language == LanguageType.UNKNOWN)
{
return;
return false;
}

// TranslationService reads the prompt template off the agent it is handed, and only the
// AI Assistant defines it. Fall back to that agent when the executing one has no template,
// which is the normal case for the task agents reaching us through InstructDirect.
// which is the normal case for the task agents handled by InstructDirect.
var host = agent;
if (host?.Templates?.Any(x => x.Name == TranslationPromptName) != true)
{
Expand All @@ -49,5 +52,7 @@ private async Task TranslateInboundMessage(Agent agent, RoleDialogModel message)
message.Content = await translator.Translate(host, message.MessageId, message.Content,
language: LanguageType.ENGLISH,
clone: false);

return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ public async Task<RoleDialogModel> InstructLoop(Agent agent, RoleDialogModel mes

await _context.Push(_router.Id);

// Handle multi-language for input
await TranslateInboundMessage(_router, message);

dialogs.Add(message);
Context.SetDialogs(dialogs);
await storage.Append(convService.ConversationId, message);
Expand Down
4 changes: 0 additions & 4 deletions src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,6 @@ public async Task<RoleDialogModel> InstructDirect(Agent agent, RoleDialogModel m
var conv = _services.GetRequiredService<IConversationService>();
var storage = _services.GetRequiredService<IConversationStorage>();

// Must run before the message is persisted so the stored record matches InstructLoop:
// Content in English, the user's original text in SecondaryContent.
await TranslateInboundMessage(agent, message);

await storage.Append(conv.ConversationId, message);

dialogs.Add(message);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,20 @@ public override async Task OnMessageReceived(RoleDialogModel message)
{
var settings = _services.GetRequiredService<ConversationSetting>();
var states = _services.GetRequiredService<IConversationStateService>();

var storage = _services.GetRequiredService<IConversationStorage>();

var convId = states.GetConversationId();
var rateLimit = settings.RateLimit;
var channel = states.GetState("channel");

// Check max input length
var charCount = message.Content.Length;
if (charCount > rateLimit.MaxInputLengthPerRequest)
{
await storage.Append(convId, message);
message.Content = $"The number of characters in your message exceeds the system maximum of {rateLimit.MaxInputLengthPerRequest}";
message.StopCompletion = true;
ClearMessage(message);
return;
}

Expand All @@ -50,8 +54,10 @@ public override async Task OnMessageReceived(RoleDialogModel message)
var seconds = (DateTime.UtcNow - userSents.First().CreatedAt).TotalSeconds;
if (seconds < rateLimit.MinTimeSecondsBetweenMessages)
{
await storage.Append(convId, message);
message.Content = "Your message sending frequency exceeds the frequency specified by the system. Please try again later.";
message.StopCompletion = true;
ClearMessage(message);
return;
}
}
Expand All @@ -69,10 +75,19 @@ public override async Task OnMessageReceived(RoleDialogModel message)

if (results.Count > rateLimit.MaxConversationPerDay)
{
await storage.Append(convId, message);
message.Content = $"The number of conversations you have exceeds the system maximum of {rateLimit.MaxConversationPerDay}";
message.StopCompletion = true;
ClearMessage(message);
return;
}
}
}

private void ClearMessage(RoleDialogModel message)
{
message.SecondaryContent = null;
message.RichContent = null;
message.SecondaryRichContent = null;
}
}
Loading