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 @@ -24,20 +24,7 @@ public async Task<RoleDialogModel> InstructLoop(Agent agent, RoleDialogModel mes
await _context.Push(_router.Id);

// Handle multi-language for input
var agentSettings = _services.GetRequiredService<AgentSettings>();
if (agentSettings.EnableTranslator)
{
var translator = _services.GetRequiredService<ITranslationService>();

var language = states.GetState(StateConst.LANGUAGE, LanguageType.ENGLISH);
if (language != LanguageType.ENGLISH)
{
message.SecondaryContent = message.Content;
message.Content = await translator.Translate(_router, message.MessageId, message.Content,
language: LanguageType.ENGLISH,
clone: false);
}
}
await TranslateInboundMessage(_router, message);

dialogs.Add(message);
Context.SetDialogs(dialogs);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using BotSharp.Abstraction.Infrastructures.Enums;

namespace BotSharp.Core.Routing;

public partial class RoutingService
{
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.
/// </summary>
private async Task TranslateInboundMessage(Agent agent, RoleDialogModel message)
{
var agentSettings = _services.GetRequiredService<AgentSettings>();
if (!agentSettings.EnableTranslator)
{
return;
}

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);
if (language == LanguageType.ENGLISH || language == LanguageType.UNKNOWN)
{
return;
}

// 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.
var host = agent;
if (host?.Templates?.Any(x => x.Name == TranslationPromptName) != true)
{
var agentService = _services.GetRequiredService<IAgentService>();
host = await agentService.LoadAgent(BuiltInAgentId.AIAssistant);
}

var translator = _services.GetRequiredService<ITranslationService>();
message.SecondaryContent = message.Content;
message.Content = await translator.Translate(host, message.MessageId, message.Content,
language: LanguageType.ENGLISH,
clone: false);
}
}
5 changes: 5 additions & 0 deletions src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ 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
Loading