From f5eaa716ea2e8678efca42a3cdee006a6e2aa804 Mon Sep 17 00:00:00 2001
From: Haiping Chen <101423@smsassist.com>
Date: Sun, 31 Mar 2024 14:28:16 -0500
Subject: [PATCH] Add featuer of VisibilityExpression
---
docs/llm/function.md | 30 +++++++++-
.../Agents/IAgentService.cs | 3 +
.../Functions/Models/FunctionDef.cs | 3 +
.../Loggers/IContentGeneratingHook.cs | 9 +++
.../Agents/Services/AgentService.LoadAgent.cs | 26 ---------
.../Agents/Services/AgentService.Rendering.cs | 55 +++++++++++++++++++
.../Agents/Services/AgentService.cs | 1 -
.../Providers/ChatCompletionProvider.cs | 13 +++--
.../Hooks/StreamingLogHook.cs | 21 +++++++
9 files changed, 128 insertions(+), 33 deletions(-)
create mode 100644 src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs
diff --git a/docs/llm/function.md b/docs/llm/function.md
index 07cfeb1e..69c4f20d 100644
--- a/docs/llm/function.md
+++ b/docs/llm/function.md
@@ -2,4 +2,32 @@
A **calling function** is a function that is passed as an argument to another function and is executed after a specific event or action occurs. In the context of **large language models (LLMs)**, calling functions can be used to hook into various stages of an LLM application. They are useful for tasks such as logging, monitoring, streaming, and more. For example, in the **BotSharp** framework, calling functions can be used to log information, monitor the progress of an LLM application, or perform other tasks. The BotSharp provides a `callbacks` argument that allows developers to interactive with external systems.
-The use of calling functions in LLM applications provides flexibility and extensibility. Developers can customize the behavior of their applications by defining callback handlers that implement specific methods. These handlers can be used for tasks like logging, error handling, or interacting with external systems. The function will be triggered by LLM based on the conversation context.
\ No newline at end of file
+The use of calling functions in LLM applications provides flexibility and extensibility. Developers can customize the behavior of their applications by defining callback handlers that implement specific methods. These handlers can be used for tasks like logging, error handling, or interacting with external systems. The function will be triggered by LLM based on the conversation context.
+
+## Hide Function
+
+In order to more flexibly control whether the Agent is allowed to use a certain function, there is a Visibility Expression property in the function definition that can be used to control display or hiding. When we input prompt into LLM, although we can use state variables in the system instruction file to control the rendering content, LLM will still take the definition of the function into consideration. If the related functions are not hidden at the same time, LLM will still be It is possible to call related functions, bringing unexpected results. Because we need to control system instruction and function definition at the same time to make them consistent.
+
+```json
+{
+ "name": "make_payment",
+ "description": "call this function to make payment",
+ "visibility_expression": "{% if states.order_number != empty %}visible{% endif %}",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "order_number": {
+ "type": "string",
+ "description": "order number."
+ },
+ "total_amount": {
+ "type": "string",
+ "description": "total amount."
+ }
+ },
+ "required": ["order_number", "total_amount"]
+ }
+}
+```
+
+The above is an example. The system will parse the liquid template of Visibility Expression `{% if states.order_number != empty %}visible{% endif %}`. When "visible" is returned, the system will allow the Agent to use this function. In liquid In expressions, we can use `states.name` to reference the state value in the conversation.
\ No newline at end of file
diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs
index 5f3b1b87..c8aee271 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs
@@ -1,3 +1,4 @@
+using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Plugins.Models;
using BotSharp.Abstraction.Repositories.Filters;
@@ -23,6 +24,8 @@ public interface IAgentService
string RenderedTemplate(Agent agent, string templateName);
+ bool RenderFunction(Agent agent, FunctionDef def);
+
///
/// Get agent detail without trigger any hook.
///
diff --git a/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionDef.cs b/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionDef.cs
index 54ef0000..8458adf6 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionDef.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionDef.cs
@@ -5,6 +5,9 @@ public class FunctionDef
public string Name { get; set; }
public string Description { get; set; }
+ [JsonPropertyName("visibility_expression")]
+ public string? VisibilityExpression { get; set; }
+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Impact { get; set; }
diff --git a/src/Infrastructure/BotSharp.Abstraction/Loggers/IContentGeneratingHook.cs b/src/Infrastructure/BotSharp.Abstraction/Loggers/IContentGeneratingHook.cs
index 44f229ae..9d0662db 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Loggers/IContentGeneratingHook.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Loggers/IContentGeneratingHook.cs
@@ -28,4 +28,13 @@ public interface IContentGeneratingHook
///
///
Task AfterGenerated(RoleDialogModel message, TokenStatsModel tokenStats) => Task.CompletedTask;
+
+ ///
+ /// Rdndering template
+ ///
+ ///
+ ///
+ ///
+ ///
+ Task OnRenderingTemplate(Agent agent, string name, string content) => Task.CompletedTask;
}
diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs
index 5d618611..6c2d7ea2 100644
--- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs
+++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs
@@ -90,32 +90,6 @@ public partial class AgentService
return agent;
}
- public string RenderedTemplate(Agent agent, string templateName)
- {
- // render liquid template
- var render = _services.GetRequiredService();
- var template = agent.Templates.First(x => x.Name == templateName).Content;
- // update states
- var conv = _services.GetRequiredService();
- foreach (var t in conv.States.GetStates())
- {
- agent.TemplateDict[t.Key] = t.Value;
- }
- return render.Render(template, agent.TemplateDict);
- }
-
- public string RenderedInstruction(Agent agent)
- {
- var render = _services.GetRequiredService();
- // update states
- var conv = _services.GetRequiredService();
- foreach (var t in conv.States.GetStates())
- {
- agent.TemplateDict[t.Key] = t.Value;
- }
- return render.Render(agent.Instruction, agent.TemplateDict);
- }
-
private void PopulateState(Dictionary dict)
{
var conv = _services.GetRequiredService();
diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs
new file mode 100644
index 00000000..9ce7a7a8
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs
@@ -0,0 +1,55 @@
+using BotSharp.Abstraction.Loggers;
+using BotSharp.Abstraction.Templating;
+
+namespace BotSharp.Core.Agents.Services;
+
+public partial class AgentService
+{
+ public string RenderedInstruction(Agent agent)
+ {
+ var render = _services.GetRequiredService();
+ // update states
+ var conv = _services.GetRequiredService();
+ foreach (var t in conv.States.GetStates())
+ {
+ agent.TemplateDict[t.Key] = t.Value;
+ }
+ return render.Render(agent.Instruction, agent.TemplateDict);
+ }
+
+ public bool RenderFunction(Agent agent, FunctionDef def)
+ {
+ if (!string.IsNullOrEmpty(def.VisibilityExpression))
+ {
+ var render = _services.GetRequiredService();
+ var result = render.Render(def.VisibilityExpression, new Dictionary
+ {
+ { "states", agent.TemplateDict }
+ });
+ return result == "visible";
+ }
+
+ return true;
+ }
+
+ public string RenderedTemplate(Agent agent, string templateName)
+ {
+ // render liquid template
+ var render = _services.GetRequiredService();
+ var template = agent.Templates.First(x => x.Name == templateName).Content;
+ // update states
+ var conv = _services.GetRequiredService();
+ foreach (var t in conv.States.GetStates())
+ {
+ agent.TemplateDict[t.Key] = t.Value;
+ }
+
+ var content = render.Render(template, agent.TemplateDict);
+
+ HookEmitter.Emit(_services, async hook =>
+ await hook.OnRenderingTemplate(agent, templateName, content)
+ ).Wait();
+
+ return content;
+ }
+}
diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs
index 28f9bdd2..bd009db0 100644
--- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs
+++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs
@@ -1,4 +1,3 @@
-using BotSharp.Abstraction.Repositories;
using System.IO;
namespace BotSharp.Core.Agents.Services;
diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs
index f50884e0..e3f6afca 100644
--- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs
+++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs
@@ -219,12 +219,15 @@ public class ChatCompletionProvider : IChatCompletion
foreach (var function in agent.Functions)
{
- chatCompletionsOptions.Functions.Add(new FunctionDefinition
+ if (agentService.RenderFunction(agent, function))
{
- Name = function.Name,
- Description = function.Description,
- Parameters = BinaryData.FromObjectAsJson(function.Parameters)
- });
+ chatCompletionsOptions.Functions.Add(new FunctionDefinition
+ {
+ Name = function.Name,
+ Description = function.Description,
+ Parameters = BinaryData.FromObjectAsJson(function.Parameters)
+ });
+ }
}
foreach (var message in conversations)
diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs
index ce6701b0..320dd21f 100644
--- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs
+++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs
@@ -72,6 +72,27 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", BuildContentLog(input));
}
+ public async Task OnRenderingTemplate(Agent agent, string name, string content)
+ {
+ if (!_convSettings.ShowVerboseLog) return;
+
+ var conversationId = _state.GetConversationId();
+
+ var log = $"{agent.Name} is using template {name}";
+ var message = new RoleDialogModel(AgentRole.System, log)
+ {
+ MessageId = _routingCtx.MessageId
+ };
+
+ var input = new ContentLogInputModel(conversationId, message)
+ {
+ Name = agent.Name,
+ Source = ContentLogSource.HardRule,
+ Log = log
+ };
+ await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", BuildContentLog(input));
+ }
+
public async Task BeforeGenerating(Agent agent, List conversations)
{
if (!_convSettings.ShowVerboseLog) return;