diff --git a/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleCriteriaEvaluator.cs b/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleCriteriaEvaluator.cs index 4f14ee622..e5f39aeb1 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleCriteriaEvaluator.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleCriteriaEvaluator.cs @@ -18,6 +18,10 @@ public interface IRuleCriteriaEvaluator /// Evaluate the criteria for a single agent's rule. /// /// The agent whose rule is being considered + /// + /// The rule being considered. An agent can carry more than one rule on the same trigger, so the + /// evaluator is handed the one under evaluation rather than resolving it from the agent itself. + /// /// The rule trigger /// The per-request criteria context /// @@ -25,5 +29,5 @@ public interface IRuleCriteriaEvaluator /// or null when the evaluator could not produce an answer (missing script/template, /// failed execution, error). /// - Task EvaluateAsync(Agent agent, IRuleTrigger trigger, RuleCriteriaContext context); + Task EvaluateAsync(Agent agent, AgentRule agentRule, IRuleTrigger trigger, RuleCriteriaContext context); } diff --git a/src/Infrastructure/BotSharp.Core.Rules/Criteria/Code/CodeCriteriaEvaluator.cs b/src/Infrastructure/BotSharp.Core.Rules/Criteria/Code/CodeCriteriaEvaluator.cs index 31a712944..618c2140e 100644 --- a/src/Infrastructure/BotSharp.Core.Rules/Criteria/Code/CodeCriteriaEvaluator.cs +++ b/src/Infrastructure/BotSharp.Core.Rules/Criteria/Code/CodeCriteriaEvaluator.cs @@ -25,7 +25,7 @@ public CodeCriteriaEvaluator( public string Type => BuiltInRuleCriteria.PythonScript; - public async Task EvaluateAsync(Agent agent, IRuleTrigger trigger, RuleCriteriaContext context) + public async Task EvaluateAsync(Agent agent, AgentRule agentRule, IRuleTrigger trigger, RuleCriteriaContext context) { var settings = context.Options.GetData() ?? new(); var provider = settings.CodeProcessor ?? BuiltInCodeProcessor.PyInterpreter; diff --git a/src/Infrastructure/BotSharp.Core.Rules/Criteria/Llm/LlmCriteriaEvaluator.cs b/src/Infrastructure/BotSharp.Core.Rules/Criteria/Llm/LlmCriteriaEvaluator.cs index 839c740fa..6aaab0844 100644 --- a/src/Infrastructure/BotSharp.Core.Rules/Criteria/Llm/LlmCriteriaEvaluator.cs +++ b/src/Infrastructure/BotSharp.Core.Rules/Criteria/Llm/LlmCriteriaEvaluator.cs @@ -30,17 +30,16 @@ public LlmCriteriaEvaluator( public string Type => BuiltInRuleCriteria.Llm; - public async Task EvaluateAsync(Agent agent, IRuleTrigger trigger, RuleCriteriaContext context) + public async Task EvaluateAsync(Agent agent, AgentRule agentRule, IRuleTrigger trigger, RuleCriteriaContext context) { var settings = context.Options.GetData() ?? new(); - var rule = agent.Rules.FirstOrDefault(x => x.TriggerName.IsEqualTo(trigger.Name)); // The Rules agent hosts the criteria-check template by default. var agentId = !string.IsNullOrWhiteSpace(settings.AgentId) ? settings.AgentId! : BuiltInAgentId.RulesInterpreter; var templateName = !string.IsNullOrWhiteSpace(settings.TemplateName) ? settings.TemplateName! : (agentId == BuiltInAgentId.RulesInterpreter ? DefaultTemplateName : $"{trigger.Name}_criteria"); - var input = BuildInput(rule?.CriteriaConfig, settings); + var input = BuildInput(agentRule.CriteriaConfig, settings); var msg = $"rule trigger ({trigger.Name}) llm criteria (agent {agentId}, template {templateName})."; try diff --git a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs index 697fdaeab..f6b906bc8 100644 --- a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs +++ b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs @@ -44,33 +44,41 @@ public async Task> Triggered(IRuleTrigger trigger, string te var filteredAgents = agents.Items.Where(x => x.Rules.Exists(r => r.TriggerName.IsEqualTo(trigger.Name) && !x.Disabled)).ToList(); foreach (var agent in filteredAgents) { - var rule = agent.Rules.FirstOrDefault(x => x.TriggerName.IsEqualTo(trigger.Name) && !x.Disabled); - if (rule == null) + var rules = agent.Rules.Where(x => x.TriggerName.IsEqualTo(trigger.Name) && !x.Disabled).ToList(); + if (rules.IsNullOrEmpty()) { continue; } - // The rule's own mode wins over the mode carried on the trigger options, so an agent can - // pick how its criteria is judged without the caller knowing. - var evaluator = ResolveCriteriaEvaluator(rule.CriteriaConfig?.Mode) ?? criteriaEvaluator; - if (evaluator != null && options?.Criteria != null) + foreach (var rule in rules) { - var criteriaContext = new RuleCriteriaContext + if (rule == null) { - Options = options.Criteria, - States = states - }; + continue; + } - var isTriggered = await EvaluateCriteria(evaluator, agent, trigger, criteriaContext); - if (!isTriggered) + // The rule's own mode wins over the mode carried on the trigger options, so an agent can + // pick how its criteria is judged without the caller knowing. + var evaluator = ResolveCriteriaEvaluator(rule.CriteriaConfig?.Mode) ?? criteriaEvaluator; + if (evaluator != null && options?.Criteria != null) { - continue; + var criteriaContext = new RuleCriteriaContext + { + Options = options.Criteria, + States = states + }; + + var isTriggered = await EvaluateCriteria(evaluator, agent, rule, trigger, criteriaContext); + if (!isTriggered) + { + continue; + } } - } - var msg = !string.IsNullOrWhiteSpace(rule.Message) ? rule.Message : text; - var convId = await SendMessageToAgent(agent, trigger, text, msg, states); - newConversationIds.Add(convId); + var msg = !string.IsNullOrWhiteSpace(rule.Message) ? rule.Message : text; + var convId = await SendMessageToAgent(agent, trigger, text, msg, states); + newConversationIds.Add(convId); + } } return newConversationIds; @@ -96,10 +104,11 @@ public async Task> Triggered(IRuleTrigger trigger, string te private async Task EvaluateCriteria( IRuleCriteriaEvaluator evaluator, Agent agent, + AgentRule agentRule, IRuleTrigger trigger, RuleCriteriaContext context) { - var isTriggered = await evaluator.EvaluateAsync(agent, trigger, context); + var isTriggered = await evaluator.EvaluateAsync(agent, agentRule, trigger, context); if (isTriggered != null) { return isTriggered.Value; @@ -118,7 +127,7 @@ private async Task EvaluateCriteria( } _logger.LogInformation($"Rule criteria evaluator ({evaluator.Type}) returned no result, falling back to llm for agent ({agent.Name}) and trigger ({trigger.Name})."); - return await llmEvaluator.EvaluateAsync(agent, trigger, context) ?? false; + return await llmEvaluator.EvaluateAsync(agent, agentRule, trigger, context) ?? false; } #endregion