commit
7ded100708
|
|
@ -1,9 +1,9 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<LangVersion>12.0</LangVersion>
|
||||
<BotSharpVersion>1.0.1</BotSharpVersion>
|
||||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
||||
<LangVersion>10.0</LangVersion>
|
||||
<BotSharpVersion>1.2.1</BotSharpVersion>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<GenerateDocumentationFile>false</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
[](https://discord.com/channels/1106946823282761851/1106947212459642991)
|
||||
[](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=sN9VVMwbWjs5L0ATpizKKxOcZdEPMrp8&authKey=RLDw41bLTrEyEgZZi%2FzT4pYk%2BwmEFgFcrhs8ZbkiVY7a4JFckzJefaYNW6Lk4yPX&noverify=0&group_code=985366726)
|
||||
[](https://gitter.im/sci-sharp/community)
|
||||
[](https://raw.githubusercontent.com/Oceania2018/BotSharp/master/LICENSE)
|
||||
[](https://www.nuget.org/packages/BotSharp.Core)
|
||||
[](https://ci.appveyor.com/project/Haiping-Chen/botsharp)
|
||||
|
|
@ -88,6 +87,7 @@ BotSharp uses component design, the kernel is kept to a minimum, and business fu
|
|||
- BotSharp.Plugin.HuggingFace
|
||||
- BotSharp.Plugin.LLamaSharp
|
||||
- BotSharp.Plugin.SemanticKernel
|
||||
- BotSharp.Plugin.SparkDesk
|
||||
|
||||
#### Messaging / Channel
|
||||
- BotSharp.OpenAPI
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
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.
|
||||
|
|
@ -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);
|
||||
|
||||
/// <summary>
|
||||
/// Get agent detail without trigger any hook.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ public abstract class ConversationHookBase : IConversationHook
|
|||
public virtual Task OnStateLoaded(ConversationState state)
|
||||
=> Task.CompletedTask;
|
||||
|
||||
public virtual Task OnStateChanged(string name, string preValue, string currentValue)
|
||||
public virtual Task OnStateChanged(StateChangeModel stateChange)
|
||||
=> Task.CompletedTask;
|
||||
|
||||
public virtual Task OnDialogRecordLoaded(RoleDialogModel dialog)
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ public interface IConversationHook
|
|||
Task OnDialogRecordLoaded(RoleDialogModel dialog);
|
||||
|
||||
Task OnStateLoaded(ConversationState state);
|
||||
Task OnStateChanged(string name, string preValue, string currentValue);
|
||||
Task OnStateChanged(StateChangeModel stateChange);
|
||||
|
||||
Task OnMessageReceived(RoleDialogModel message);
|
||||
Task OnPostbackMessageReceived(RoleDialogModel message, PostbackMessageModel replyMsg);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
namespace BotSharp.Abstraction.Conversations.Models;
|
||||
|
||||
public class StateChangeModel
|
||||
{
|
||||
[JsonPropertyName("conversation_id")]
|
||||
public string ConversationId { get; set; }
|
||||
|
||||
[JsonPropertyName("message_id")]
|
||||
public string MessageId { get; set; }
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonPropertyName("before_value")]
|
||||
public string BeforeValue { get; set; }
|
||||
|
||||
[JsonPropertyName("before_active_rounds")]
|
||||
public int? BeforeActiveRounds { get; set; }
|
||||
|
||||
[JsonPropertyName("after_value")]
|
||||
public string AfterValue { get; set; }
|
||||
|
||||
[JsonPropertyName("after_active_rounds")]
|
||||
public int? AfterActiveRounds { get; set; }
|
||||
}
|
||||
|
|
@ -18,4 +18,9 @@ public class FunctionCallingResponse
|
|||
|
||||
[JsonPropertyName("args")]
|
||||
public JsonDocument? Args { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{FunctionName}({JsonSerializer.Serialize(Args)}) => {Content}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
namespace BotSharp.Abstraction.Infrastructures.Enums;
|
||||
|
||||
public class StateConst
|
||||
{
|
||||
public const string EXPECTED_ACTION_AGENT = "expected_next_action_agent";
|
||||
public const string EXPECTED_GOAL_AGENT = "expected_user_goal_agent";
|
||||
public const string NEXT_ACTION_AGENT = "next_action_agent";
|
||||
public const string USER_GOAL_AGENT = "user_goal_agent";
|
||||
}
|
||||
|
|
@ -28,4 +28,13 @@ public interface IContentGeneratingHook
|
|||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task AfterGenerated(RoleDialogModel message, TokenStatsModel tokenStats) => Task.CompletedTask;
|
||||
|
||||
/// <summary>
|
||||
/// Rdndering template
|
||||
/// </summary>
|
||||
/// <param name="agent"></param>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="content"></param>
|
||||
/// <returns></returns>
|
||||
Task OnRenderingTemplate(Agent agent, string name, string content) => Task.CompletedTask;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
namespace BotSharp.Abstraction.Loggers.Models;
|
||||
|
||||
public class AgentQueueChangedLogModel
|
||||
{
|
||||
[JsonPropertyName("conversation_id")]
|
||||
public string ConversationId { get; set; }
|
||||
|
||||
[JsonPropertyName("log")]
|
||||
public string Log { get; set; }
|
||||
|
||||
[JsonPropertyName("created_at")]
|
||||
public DateTime CreateTime { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
namespace BotSharp.Abstraction.Loggers.Models;
|
||||
|
||||
public class StateChangeOutputModel : StateChangeModel
|
||||
{
|
||||
[JsonPropertyName("created_at")]
|
||||
public DateTime CreateTime { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
|
@ -10,15 +10,7 @@ public class RichContentJsonConverter : JsonConverter<IRichMessage>
|
|||
using var jsonDoc = JsonDocument.ParseValue(ref reader);
|
||||
var root = jsonDoc.RootElement;
|
||||
var jsonText = root.GetRawText();
|
||||
IRichMessage? res = null;
|
||||
|
||||
var parser = new MessageParser();
|
||||
if (root.TryGetProperty("rich_type", out JsonElement element))
|
||||
{
|
||||
var richType = element.GetString();
|
||||
res = parser.ParseRichMessage(richType, jsonText, root, options);
|
||||
}
|
||||
|
||||
var res = MessageParser.ParseRichMessage(root, options);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,15 +10,7 @@ public class TemplateMessageJsonConverter : JsonConverter<ITemplateMessage>
|
|||
using var jsonDoc = JsonDocument.ParseValue(ref reader);
|
||||
var root = jsonDoc.RootElement;
|
||||
var jsonText = root.GetRawText();
|
||||
ITemplateMessage? res = null;
|
||||
|
||||
var parser = new MessageParser();
|
||||
if (root.TryGetProperty("template_type", out JsonElement element))
|
||||
{
|
||||
var templateType = element.GetString();
|
||||
res = parser.ParseTemplateMessage(templateType, jsonText, root, options);
|
||||
}
|
||||
|
||||
var res = MessageParser.ParseTemplateMessage(root, options);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,44 +6,51 @@ using System.Text.Json;
|
|||
|
||||
namespace BotSharp.Core.Messaging;
|
||||
|
||||
public class MessageParser
|
||||
public static class MessageParser
|
||||
{
|
||||
public MessageParser()
|
||||
{
|
||||
}
|
||||
|
||||
public IRichMessage? ParseRichMessage(string richType, string jsonText, JsonElement root, JsonSerializerOptions options)
|
||||
public static IRichMessage? ParseRichMessage(JsonElement root, JsonSerializerOptions options)
|
||||
{
|
||||
IRichMessage? res = null;
|
||||
JsonElement element;
|
||||
var jsonText = root.GetRawText();
|
||||
|
||||
if (richType == RichTypeEnum.ButtonTemplate)
|
||||
if (root.TryGetProperty("rich_type", out element))
|
||||
{
|
||||
res = JsonSerializer.Deserialize<ButtonTemplateMessage>(jsonText, options);
|
||||
}
|
||||
else if (richType == RichTypeEnum.MultiSelectTemplate)
|
||||
{
|
||||
res = JsonSerializer.Deserialize<MultiSelectTemplateMessage>(jsonText, options);
|
||||
}
|
||||
else if (richType == RichTypeEnum.QuickReply)
|
||||
{
|
||||
res = JsonSerializer.Deserialize<QuickReplyMessage>(jsonText, options);
|
||||
}
|
||||
else if (richType == RichTypeEnum.CouponTemplate)
|
||||
{
|
||||
res = JsonSerializer.Deserialize<CouponTemplateMessage>(jsonText, options);
|
||||
}
|
||||
else if (richType == RichTypeEnum.Text)
|
||||
{
|
||||
res = JsonSerializer.Deserialize<TextMessage>(jsonText, options);
|
||||
}
|
||||
else if (richType == RichTypeEnum.GenericTemplate)
|
||||
{
|
||||
if (root.TryGetProperty("element_type", out var element))
|
||||
var richType = element.GetString();
|
||||
if (richType == RichTypeEnum.ButtonTemplate)
|
||||
{
|
||||
var elementType = element.GetString();
|
||||
if (elementType == typeof(GenericElement).Name)
|
||||
res = JsonSerializer.Deserialize<ButtonTemplateMessage>(jsonText, options);
|
||||
}
|
||||
else if (richType == RichTypeEnum.MultiSelectTemplate)
|
||||
{
|
||||
res = JsonSerializer.Deserialize<MultiSelectTemplateMessage>(jsonText, options);
|
||||
}
|
||||
else if (richType == RichTypeEnum.QuickReply)
|
||||
{
|
||||
res = JsonSerializer.Deserialize<QuickReplyMessage>(jsonText, options);
|
||||
}
|
||||
else if (richType == RichTypeEnum.CouponTemplate)
|
||||
{
|
||||
res = JsonSerializer.Deserialize<CouponTemplateMessage>(jsonText, options);
|
||||
}
|
||||
else if (richType == RichTypeEnum.Text)
|
||||
{
|
||||
res = JsonSerializer.Deserialize<TextMessage>(jsonText, options);
|
||||
}
|
||||
else if (richType == RichTypeEnum.GenericTemplate)
|
||||
{
|
||||
if (root.TryGetProperty("element_type", out element))
|
||||
{
|
||||
res = JsonSerializer.Deserialize<GenericTemplateMessage<GenericElement>>(jsonText, options);
|
||||
var elementType = element.GetString();
|
||||
if (elementType == typeof(GenericElement).Name)
|
||||
{
|
||||
res = JsonSerializer.Deserialize<GenericTemplateMessage<GenericElement>>(jsonText, options);
|
||||
}
|
||||
else if (elementType == typeof(ButtonElement).Name)
|
||||
{
|
||||
res = JsonSerializer.Deserialize<GenericTemplateMessage<ButtonElement>>(jsonText, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -51,34 +58,44 @@ public class MessageParser
|
|||
return res;
|
||||
}
|
||||
|
||||
public ITemplateMessage? ParseTemplateMessage(string templateType, string jsonText, JsonElement root, JsonSerializerOptions options)
|
||||
public static ITemplateMessage? ParseTemplateMessage(JsonElement root, JsonSerializerOptions options)
|
||||
{
|
||||
ITemplateMessage? res = null;
|
||||
JsonElement element;
|
||||
var jsonText = root.GetRawText();
|
||||
|
||||
if (templateType == TemplateTypeEnum.Button)
|
||||
if (root.TryGetProperty("template_type", out element))
|
||||
{
|
||||
res = JsonSerializer.Deserialize<ButtonTemplateMessage>(jsonText, options);
|
||||
}
|
||||
else if (templateType == TemplateTypeEnum.MultiSelect)
|
||||
{
|
||||
res = JsonSerializer.Deserialize<MultiSelectTemplateMessage>(jsonText, options);
|
||||
}
|
||||
else if (templateType == TemplateTypeEnum.Coupon)
|
||||
{
|
||||
res = JsonSerializer.Deserialize<CouponTemplateMessage>(jsonText, options);
|
||||
}
|
||||
else if (templateType == TemplateTypeEnum.Product)
|
||||
{
|
||||
res = JsonSerializer.Deserialize<ProductTemplateMessage>(jsonText, options);
|
||||
}
|
||||
else if (templateType == TemplateTypeEnum.Generic)
|
||||
{
|
||||
if (root.TryGetProperty("element_type", out var element))
|
||||
var templateType = element.GetString();
|
||||
if (templateType == TemplateTypeEnum.Button)
|
||||
{
|
||||
var elementType = element.GetString();
|
||||
if (elementType == typeof(GenericElement).Name)
|
||||
res = JsonSerializer.Deserialize<ButtonTemplateMessage>(jsonText, options);
|
||||
}
|
||||
else if (templateType == TemplateTypeEnum.MultiSelect)
|
||||
{
|
||||
res = JsonSerializer.Deserialize<MultiSelectTemplateMessage>(jsonText, options);
|
||||
}
|
||||
else if (templateType == TemplateTypeEnum.Coupon)
|
||||
{
|
||||
res = JsonSerializer.Deserialize<CouponTemplateMessage>(jsonText, options);
|
||||
}
|
||||
else if (templateType == TemplateTypeEnum.Product)
|
||||
{
|
||||
res = JsonSerializer.Deserialize<ProductTemplateMessage>(jsonText, options);
|
||||
}
|
||||
else if (templateType == TemplateTypeEnum.Generic)
|
||||
{
|
||||
if (root.TryGetProperty("element_type", out element))
|
||||
{
|
||||
res = JsonSerializer.Deserialize<GenericTemplateMessage<GenericElement>>(jsonText, options);
|
||||
var elementType = element.GetString();
|
||||
if (elementType == typeof(GenericElement).Name)
|
||||
{
|
||||
res = JsonSerializer.Deserialize<GenericTemplateMessage<GenericElement>>(jsonText, options);
|
||||
}
|
||||
else if (elementType == typeof(ButtonElement).Name)
|
||||
{
|
||||
res = JsonSerializer.Deserialize<GenericTemplateMessage<ButtonElement>>(jsonText, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using BotSharp.Abstraction.Messaging.JsonConverters;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace BotSharp.Abstraction.Options;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ public class RoutingRule
|
|||
/// <summary>
|
||||
/// Field type: string, number, object
|
||||
/// </summary>
|
||||
[JsonPropertyName("field_type")]
|
||||
public string FieldType { get; set; } = "string";
|
||||
|
||||
public bool Required { get; set; }
|
||||
|
|
|
|||
|
|
@ -9,4 +9,8 @@ global using BotSharp.Abstraction.Agents.Models;
|
|||
global using BotSharp.Abstraction.Utilities;
|
||||
global using BotSharp.Abstraction.Conversations.Models;
|
||||
global using BotSharp.Abstraction.Agents.Enums;
|
||||
global using BotSharp.Abstraction.Models;
|
||||
global using BotSharp.Abstraction.Infrastructures.Enums;
|
||||
global using BotSharp.Abstraction.Models;
|
||||
global using BotSharp.Abstraction.Routing.Models;
|
||||
global using BotSharp.Abstraction.Routing.Planning;
|
||||
global using BotSharp.Abstraction.Templating;
|
||||
|
|
|
|||
|
|
@ -90,32 +90,6 @@ public partial class AgentService
|
|||
return agent;
|
||||
}
|
||||
|
||||
public string RenderedTemplate(Agent agent, string templateName)
|
||||
{
|
||||
// render liquid template
|
||||
var render = _services.GetRequiredService<ITemplateRender>();
|
||||
var template = agent.Templates.First(x => x.Name == templateName).Content;
|
||||
// update states
|
||||
var conv = _services.GetRequiredService<IConversationService>();
|
||||
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<ITemplateRender>();
|
||||
// update states
|
||||
var conv = _services.GetRequiredService<IConversationService>();
|
||||
foreach (var t in conv.States.GetStates())
|
||||
{
|
||||
agent.TemplateDict[t.Key] = t.Value;
|
||||
}
|
||||
return render.Render(agent.Instruction, agent.TemplateDict);
|
||||
}
|
||||
|
||||
private void PopulateState(Dictionary<string, object> dict)
|
||||
{
|
||||
var conv = _services.GetRequiredService<IConversationService>();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,4 @@
|
|||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Repositories;
|
||||
using BotSharp.Abstraction.Tasks.Models;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace BotSharp.Core.Agents.Services;
|
||||
|
|
|
|||
|
|
@ -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<ITemplateRender>();
|
||||
// update states
|
||||
var conv = _services.GetRequiredService<IConversationService>();
|
||||
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<ITemplateRender>();
|
||||
var result = render.Render(def.VisibilityExpression, new Dictionary<string, object>
|
||||
{
|
||||
{ "states", agent.TemplateDict }
|
||||
});
|
||||
return result == "visible";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public string RenderedTemplate(Agent agent, string templateName)
|
||||
{
|
||||
// render liquid template
|
||||
var render = _services.GetRequiredService<ITemplateRender>();
|
||||
var template = agent.Templates.First(x => x.Name == templateName).Content;
|
||||
// update states
|
||||
var conv = _services.GetRequiredService<IConversationService>();
|
||||
foreach (var t in conv.States.GetStates())
|
||||
{
|
||||
agent.TemplateDict[t.Key] = t.Value;
|
||||
}
|
||||
|
||||
var content = render.Render(template, agent.TemplateDict);
|
||||
|
||||
HookEmitter.Emit<IContentGeneratingHook>(_services, async hook =>
|
||||
await hook.OnRenderingTemplate(agent, templateName, content)
|
||||
).Wait();
|
||||
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
using BotSharp.Abstraction.Repositories;
|
||||
using System.IO;
|
||||
|
||||
namespace BotSharp.Core.Agents.Services;
|
||||
|
|
|
|||
|
|
@ -148,15 +148,6 @@ public partial class ConversationService
|
|||
};
|
||||
|
||||
var hooks = _services.GetServices<IConversationHook>().ToList();
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
await hook.OnResponseGenerated(response);
|
||||
}
|
||||
|
||||
await onResponseReceived(response);
|
||||
|
||||
// Add to dialog history
|
||||
_storage.Append(_conversationId, response);
|
||||
|
||||
if (response.Instruction != null)
|
||||
{
|
||||
|
|
@ -172,5 +163,15 @@ public partial class ConversationService
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
await hook.OnResponseGenerated(response);
|
||||
}
|
||||
|
||||
await onResponseReceived(response);
|
||||
|
||||
// Add to dialog history
|
||||
_storage.Append(_conversationId, response);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,45 +43,56 @@ public class ConversationStateService : IConversationStateService, IDisposable
|
|||
var preValue = string.Empty;
|
||||
var currentValue = value.ToString();
|
||||
var hooks = _services.GetServices<IConversationHook>();
|
||||
var curActiveRounds = activeRounds > 0 ? activeRounds : -1;
|
||||
int? preActiveRounds = null;
|
||||
|
||||
if (ContainsState(name) && _states.TryGetValue(name, out var pair))
|
||||
{
|
||||
preValue = pair?.Values.LastOrDefault()?.Data ?? string.Empty;
|
||||
var lastNode = pair?.Values?.LastOrDefault();
|
||||
preActiveRounds = lastNode?.ActiveRounds;
|
||||
preValue = lastNode?.Data ?? string.Empty;
|
||||
}
|
||||
|
||||
if (!ContainsState(name) || preValue != currentValue)
|
||||
_logger.LogInformation($"[STATE] {name} = {value}");
|
||||
var routingCtx = _services.GetRequiredService<IRoutingContext>();
|
||||
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
_logger.LogInformation($"[STATE] {name} = {value}");
|
||||
foreach (var hook in hooks)
|
||||
hook.OnStateChanged(new StateChangeModel
|
||||
{
|
||||
hook.OnStateChanged(name, preValue, currentValue).Wait();
|
||||
}
|
||||
|
||||
var routingCtx = _services.GetRequiredService<IRoutingContext>();
|
||||
var newPair = new StateKeyValue
|
||||
{
|
||||
Key = name,
|
||||
Versioning = isNeedVersion
|
||||
};
|
||||
|
||||
var newValue = new StateValue
|
||||
{
|
||||
Data = currentValue,
|
||||
ConversationId = _conversationId,
|
||||
MessageId = routingCtx.MessageId,
|
||||
Active = true,
|
||||
ActiveRounds = activeRounds > 0 ? activeRounds : -1,
|
||||
UpdateTime = DateTime.UtcNow,
|
||||
};
|
||||
Name = name,
|
||||
BeforeValue = preValue,
|
||||
BeforeActiveRounds = preActiveRounds,
|
||||
AfterValue = currentValue,
|
||||
AfterActiveRounds = curActiveRounds
|
||||
}).Wait();
|
||||
}
|
||||
|
||||
if (!isNeedVersion || !_states.ContainsKey(name))
|
||||
{
|
||||
newPair.Values = new List<StateValue> { newValue };
|
||||
_states[name] = newPair;
|
||||
}
|
||||
else
|
||||
{
|
||||
_states[name].Values.Add(newValue);
|
||||
}
|
||||
var newPair = new StateKeyValue
|
||||
{
|
||||
Key = name,
|
||||
Versioning = isNeedVersion
|
||||
};
|
||||
|
||||
var newValue = new StateValue
|
||||
{
|
||||
Data = currentValue,
|
||||
MessageId = routingCtx.MessageId,
|
||||
Active = true,
|
||||
ActiveRounds = curActiveRounds,
|
||||
UpdateTime = DateTime.UtcNow,
|
||||
};
|
||||
|
||||
if (!isNeedVersion || !_states.ContainsKey(name))
|
||||
{
|
||||
newPair.Values = new List<StateValue> { newValue };
|
||||
_states[name] = newPair;
|
||||
}
|
||||
else
|
||||
{
|
||||
_states[name].Values.Add(newValue);
|
||||
}
|
||||
|
||||
return this;
|
||||
|
|
@ -118,7 +129,7 @@ public class ConversationStateService : IConversationStateService, IDisposable
|
|||
state.Value.Values.Add(new StateValue
|
||||
{
|
||||
Data = value.Data,
|
||||
MessageId = !string.IsNullOrEmpty(curMsgId) ? curMsgId : value.MessageId,
|
||||
MessageId = curMsgId,
|
||||
Active = false,
|
||||
ActiveRounds = value.ActiveRounds,
|
||||
UpdateTime = DateTime.UtcNow
|
||||
|
|
@ -178,7 +189,7 @@ public class ConversationStateService : IConversationStateService, IDisposable
|
|||
value.Values.Add(new StateValue
|
||||
{
|
||||
Data = lastValue.Data,
|
||||
MessageId = !string.IsNullOrEmpty(curMsgId) ? curMsgId : lastValue.MessageId,
|
||||
MessageId = curMsgId,
|
||||
Active = false,
|
||||
ActiveRounds = lastValue.ActiveRounds,
|
||||
UpdateTime = utcNow
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using BotSharp.Abstraction.Messaging;
|
||||
using BotSharp.Abstraction.Messaging.JsonConverters;
|
||||
using BotSharp.Abstraction.Messaging.Models.RichContent;
|
||||
using BotSharp.Abstraction.Options;
|
||||
using System.IO;
|
||||
|
|
@ -10,7 +9,7 @@ public class ConversationStorage : IConversationStorage
|
|||
{
|
||||
private readonly BotSharpDatabaseSettings _dbSettings;
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly JsonSerializerOptions _options;
|
||||
private readonly JsonSerializerOptions _jsonOptions;
|
||||
|
||||
public ConversationStorage(
|
||||
BotSharpDatabaseSettings dbSettings,
|
||||
|
|
@ -19,17 +18,7 @@ public class ConversationStorage : IConversationStorage
|
|||
{
|
||||
_dbSettings = dbSettings;
|
||||
_services = services;
|
||||
_options = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
AllowTrailingCommas = true,
|
||||
Converters =
|
||||
{
|
||||
new RichContentJsonConverter(),
|
||||
new TemplateMessageJsonConverter(),
|
||||
}
|
||||
};
|
||||
_jsonOptions = InitJsonSerilizerOptions(options);
|
||||
}
|
||||
|
||||
public void Append(string conversationId, RoleDialogModel dialog)
|
||||
|
|
@ -38,6 +27,13 @@ public class ConversationStorage : IConversationStorage
|
|||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var dialogElements = new List<DialogElement>();
|
||||
|
||||
// Prevent duplicate record to be inserted
|
||||
var dialogs = db.GetConversationDialogs(conversationId);
|
||||
if (dialogs.Any(x => x.MetaData.MessageId == dialog.MessageId && x.Content == dialog.Content))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (dialog.Role == AgentRole.Function)
|
||||
{
|
||||
var meta = new DialogMetaData
|
||||
|
|
@ -73,7 +69,8 @@ public class ConversationStorage : IConversationStorage
|
|||
{
|
||||
return;
|
||||
}
|
||||
var richContent = dialog.RichContent != null ? JsonSerializer.Serialize(dialog.RichContent, _options) : null;
|
||||
|
||||
var richContent = dialog.RichContent != null ? JsonSerializer.Serialize(dialog.RichContent, _jsonOptions) : null;
|
||||
dialogElements.Add(new DialogElement(meta, content, richContent));
|
||||
}
|
||||
|
||||
|
|
@ -98,7 +95,7 @@ public class ConversationStorage : IConversationStorage
|
|||
var senderId = role == AgentRole.Function ? currentAgentId : meta.SenderId;
|
||||
var createdAt = meta.CreateTime;
|
||||
var richContent = !string.IsNullOrEmpty(dialog.RichContent) ?
|
||||
JsonSerializer.Deserialize<RichContent<IRichMessage>>(dialog.RichContent, _options) : null;
|
||||
JsonSerializer.Deserialize<RichContent<IRichMessage>>(dialog.RichContent, _jsonOptions) : null;
|
||||
|
||||
var record = new RoleDialogModel(role, content)
|
||||
{
|
||||
|
|
@ -143,4 +140,21 @@ public class ConversationStorage : IConversationStorage
|
|||
}
|
||||
return Path.Combine(dir, "dialogs.txt");
|
||||
}
|
||||
|
||||
private JsonSerializerOptions InitJsonSerilizerOptions(BotSharpOptions botSharOptions)
|
||||
{
|
||||
var options = botSharOptions.JsonSerializerOptions;
|
||||
var jsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = options.PropertyNameCaseInsensitive,
|
||||
PropertyNamingPolicy = options.PropertyNamingPolicy ?? JsonNamingPolicy.CamelCase,
|
||||
AllowTrailingCommas = options.AllowTrailingCommas,
|
||||
};
|
||||
|
||||
foreach (var converter in options.Converters)
|
||||
{
|
||||
jsonOptions.Converters.Add(converter);
|
||||
}
|
||||
return jsonOptions;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,13 +19,19 @@ public class LlmProviderService : ILlmProviderService
|
|||
{
|
||||
var providers = new List<string>();
|
||||
var services1 = _services.GetServices<ITextCompletion>();
|
||||
providers.AddRange(services1.Select(x => x.Provider));
|
||||
providers.AddRange(services1
|
||||
.Where(x => GetProviderModels(x.Provider).Any())
|
||||
.Select(x => x.Provider));
|
||||
|
||||
var services2 = _services.GetServices<IChatCompletion>();
|
||||
providers.AddRange(services2.Select(x => x.Provider));
|
||||
providers.AddRange(services2
|
||||
.Where(x => GetProviderModels(x.Provider).Any())
|
||||
.Select(x => x.Provider));
|
||||
|
||||
var services3 = _services.GetServices<ITextEmbedding>();
|
||||
providers.AddRange(services3.Select(x => x.Provider));
|
||||
providers.AddRange(services3
|
||||
.Where(x => GetProviderModels(x.Provider).Any())
|
||||
.Select(x => x.Provider));
|
||||
|
||||
return providers.Distinct().ToList();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -407,6 +407,11 @@ namespace BotSharp.Core.Repository
|
|||
var utcNow = DateTime.UtcNow;
|
||||
var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir);
|
||||
|
||||
if (!Directory.Exists(dir))
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
|
||||
if (batchSize <= 0 || batchSize > batchLimit)
|
||||
{
|
||||
batchSize = batchLimit;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ public class HumanInterventionNeededHandler : RoutingHandlerBase, IRoutingHandle
|
|||
{
|
||||
new ParameterPropertyDef("reason", "why need customer service"),
|
||||
new ParameterPropertyDef("summary", "the whole conversation summary with important information"),
|
||||
new ParameterPropertyDef("response", "tell the user that you are being transferred to customer service")
|
||||
new ParameterPropertyDef("response", "asking user whether to connect with customer service representative")
|
||||
};
|
||||
|
||||
public HumanInterventionNeededHandler(IServiceProvider services, ILogger<HumanInterventionNeededHandler> logger, RoutingSettings settings)
|
||||
|
|
|
|||
|
|
@ -6,12 +6,13 @@ public class ResponseToUserRoutingHandler : RoutingHandlerBase, IRoutingHandler
|
|||
{
|
||||
public string Name => "response_to_user";
|
||||
|
||||
public string Description => "Response according to the context without asking specific agent.";
|
||||
public string Description => "When you can handle the conversation without asking specific agent.";
|
||||
|
||||
public List<ParameterPropertyDef> Parameters => new List<ParameterPropertyDef>
|
||||
{
|
||||
new ParameterPropertyDef("reason", "why response to user"),
|
||||
new ParameterPropertyDef("response", "response content")
|
||||
new ParameterPropertyDef("reason", "why response to user directly without go to other agents"),
|
||||
new ParameterPropertyDef("response", "response content to user in courteous words. If the user wants to end the conversation, you must set conversation_end to true and response politely."),
|
||||
new ParameterPropertyDef("conversation_end", "whether to end this conversation, true or false", type: "boolean")
|
||||
};
|
||||
|
||||
public ResponseToUserRoutingHandler(IServiceProvider services, ILogger<ResponseToUserRoutingHandler> logger, RoutingSettings settings)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using BotSharp.Abstraction.Infrastructures.Enums;
|
||||
using BotSharp.Abstraction.Routing.Settings;
|
||||
|
||||
namespace BotSharp.Core.Routing.Handlers;
|
||||
|
|
@ -6,11 +7,11 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler
|
|||
{
|
||||
public string Name => "route_to_agent";
|
||||
|
||||
public string Description => "Route request to appropriate agent.";
|
||||
public string Description => "Route request to appropriate virtual agent.";
|
||||
|
||||
public List<ParameterPropertyDef> Parameters => new List<ParameterPropertyDef>
|
||||
{
|
||||
new ParameterPropertyDef("next_action_reason", "the reason why route to this agent")
|
||||
new ParameterPropertyDef("next_action_reason", "the reason why route to this virtual agent")
|
||||
{
|
||||
Required = true
|
||||
},
|
||||
|
|
@ -18,11 +19,11 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler
|
|||
{
|
||||
Required = true
|
||||
},
|
||||
new ParameterPropertyDef("user_goal_description", "user original goal")
|
||||
new ParameterPropertyDef("user_goal_description", "user goal based on user initial task.")
|
||||
{
|
||||
Required = true
|
||||
},
|
||||
new ParameterPropertyDef("user_goal_agent", "user original goal")
|
||||
new ParameterPropertyDef("user_goal_agent", "agent who can acheive user initial task, must align with user_goal_description ")
|
||||
{
|
||||
Required = true
|
||||
},
|
||||
|
|
@ -39,11 +40,8 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler
|
|||
|
||||
public async Task<bool> Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message)
|
||||
{
|
||||
message.FunctionArgs = JsonSerializer.Serialize(inst);
|
||||
var ret = await routing.InvokeFunction(message.FunctionName, message);
|
||||
|
||||
var states = _services.GetRequiredService<IConversationStateService>();
|
||||
var goalAgent = states.GetState("user_goal_agent");
|
||||
var goalAgent = states.GetState(StateConst.EXPECTED_GOAL_AGENT);
|
||||
if (!string.IsNullOrEmpty(goalAgent) && inst.OriginalAgent != goalAgent)
|
||||
{
|
||||
inst.OriginalAgent = goalAgent;
|
||||
|
|
@ -53,6 +51,9 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler
|
|||
);
|
||||
}
|
||||
|
||||
message.FunctionArgs = JsonSerializer.Serialize(inst);
|
||||
var ret = await routing.InvokeFunction(message.FunctionName, message);
|
||||
|
||||
var agentId = routing.Context.GetCurrentAgentId();
|
||||
|
||||
// Update next action agent's name
|
||||
|
|
|
|||
|
|
@ -27,6 +27,32 @@ public class RoutingAgentHook : AgentHookBase
|
|||
|
||||
var routing = _services.GetRequiredService<IRoutingService>();
|
||||
var agents = routing.GetRoutableAgents(_agent.Profiles);
|
||||
|
||||
// Postprocess agent required fields, remove it if the states exists
|
||||
var states = _services.GetRequiredService<IConversationStateService>();
|
||||
foreach (var agent in agents)
|
||||
{
|
||||
var fields = agent.RequiredFields.ToArray();
|
||||
foreach (var field in fields)
|
||||
{
|
||||
if (states.ContainsState(field.Name))
|
||||
{
|
||||
var requiredField = agent.RequiredFields.First(x => x.Name == field.Name);
|
||||
agent.RequiredFields.Remove(requiredField);
|
||||
}
|
||||
}
|
||||
|
||||
fields = agent.OptionalFields.ToArray();
|
||||
foreach (var field in fields)
|
||||
{
|
||||
if (states.ContainsState(field.Name))
|
||||
{
|
||||
var optionalField = agent.OptionalFields.First(x => x.Name == field.Name);
|
||||
agent.OptionalFields.Remove(optionalField);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dict["routing_agents"] = agents;
|
||||
dict["routing_handlers"] = routing.GetHandlers(_agent);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
using Amazon.Runtime.Internal.Transform;
|
||||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Repositories.Filters;
|
||||
using BotSharp.Abstraction.Infrastructures.Enums;
|
||||
using BotSharp.Abstraction.Routing.Models;
|
||||
using BotSharp.Abstraction.Routing.Planning;
|
||||
using BotSharp.Abstraction.Templating;
|
||||
|
|
@ -116,7 +113,8 @@ public class NaivePlanner : IPlaner
|
|||
var render = _services.GetRequiredService<ITemplateRender>();
|
||||
return render.Render(template, new Dictionary<string, object>
|
||||
{
|
||||
{ "expected_next_action_agent", states.GetState("expected_next_action_agent")}
|
||||
{ StateConst.EXPECTED_ACTION_AGENT, states.GetState(StateConst.EXPECTED_ACTION_AGENT) },
|
||||
{ StateConst.EXPECTED_GOAL_AGENT, states.GetState(StateConst.EXPECTED_GOAL_AGENT) }
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,12 @@ public partial class RoutingService
|
|||
}
|
||||
else
|
||||
{
|
||||
// Handle output routing exception.
|
||||
if (agent.Type == AgentType.Routing)
|
||||
{
|
||||
response.Content = "Apologies, I'm not quite sure I understand. Could you please provide additional clarification or context?";
|
||||
}
|
||||
|
||||
message = RoleDialogModel.From(message,
|
||||
role: AgentRole.Assistant,
|
||||
content: response.Content);
|
||||
|
|
@ -74,9 +80,11 @@ public partial class RoutingService
|
|||
else
|
||||
{
|
||||
// Save to memory dialogs
|
||||
dialogs.Add(RoleDialogModel.From(message,
|
||||
var msg = RoleDialogModel.From(message,
|
||||
role: AgentRole.Function,
|
||||
content: message.Content));
|
||||
content: message.Content);
|
||||
|
||||
dialogs.Add(msg);
|
||||
|
||||
// Send to Next LLM
|
||||
var agentId = routing.Context.GetCurrentAgentId();
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ namespace BotSharp.Core.Routing;
|
|||
|
||||
public partial class RoutingService
|
||||
{
|
||||
private List<FunctionCallingResponse> _functionCallStack = new List<FunctionCallingResponse>();
|
||||
public async Task<bool> InvokeFunction(string name, RoleDialogModel message)
|
||||
{
|
||||
var function = _services.GetServices<IFunctionCallback>().FirstOrDefault(x => x.Name == name);
|
||||
|
|
@ -35,6 +36,13 @@ public partial class RoutingService
|
|||
try
|
||||
{
|
||||
result = await function.Execute(message);
|
||||
_functionCallStack.Add(new FunctionCallingResponse
|
||||
{
|
||||
Role = AgentRole.Function,
|
||||
FunctionName = message.FunctionName,
|
||||
Args = JsonDocument.Parse(message.FunctionArgs ?? "{}"),
|
||||
Content = message.Content
|
||||
});
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
|
|
@ -68,6 +76,13 @@ public partial class RoutingService
|
|||
message.FunctionName = originalFunctionName;
|
||||
}
|
||||
|
||||
// Save to Storage as well
|
||||
if (!message.StopCompletion && message.FunctionName != "route_to_agent")
|
||||
{
|
||||
var storage = _services.GetRequiredService<IConversationStorage>();
|
||||
storage.Append(Context.ConversationId, message);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,18 +4,18 @@ You're {{router.name}} ({{router.description}}). Follow these steps to handle us
|
|||
3. Determine which agent is suitable to handle this conversation.
|
||||
4. Re-think on whether the function you chose matches the reason.
|
||||
5. For agent required arguments, think carefully, leave it as blank object if user doesn't provide specific arguments.
|
||||
6. Please do not make up any parameters when there is no exact information available, leave it blank.
|
||||
6. Please do not make up any parameters when there is no exact value provided, you must set the parameter value as null.
|
||||
7. Response must be in JSON format.
|
||||
|
||||
{% if routing_requirements and routing_requirements != empty %}
|
||||
[REQUIREMENTS]
|
||||
{% for requirement in routing_requirements %}
|
||||
# {{ requirement }}
|
||||
{% endfor %}
|
||||
{% for requirement in routing_requirements -%}
|
||||
# {{ requirement }}{{ "\r\n" }}
|
||||
{%- endfor %}
|
||||
{% endif %}
|
||||
|
||||
[FUNCTIONS]
|
||||
{% for handler in routing_handlers %}
|
||||
{% for handler in routing_handlers -%}
|
||||
# {{ handler.description}}
|
||||
{% if handler.parameters and handler.parameters != empty -%}
|
||||
Parameters:
|
||||
|
|
@ -24,25 +24,29 @@ Parameters:
|
|||
- {{ p.name }} {% if p.required -%}(required){%- endif %}: {{ p.description }}{{ "\r\n " }}
|
||||
{%- endfor %}
|
||||
{%- endif %}
|
||||
{% endfor %}
|
||||
{{ "\r\n" }}
|
||||
{%- endfor %}
|
||||
|
||||
[AGENTS]
|
||||
{% for agent in routing_agents %}
|
||||
{% for agent in routing_agents -%}
|
||||
* Agent: {{ agent.name }}
|
||||
{{ agent.description}}
|
||||
{% if agent.required_fields and agent.required_fields != empty -%}
|
||||
{%- if agent.required_fields and agent.required_fields != empty -%}
|
||||
{{ "\r\n" }}
|
||||
Required args:
|
||||
{% for f in agent.required_fields -%}
|
||||
- {{ f.name }} (type: {{ f.type }}): {{ f.description }}{{ "\r\n " }}
|
||||
{%- endfor %}
|
||||
{%- endif %}
|
||||
{% if agent.optional_fields and agent.optional_fields != empty -%}
|
||||
{%- endif -%}
|
||||
{{ "\r\n" }}
|
||||
{%- if agent.optional_fields and agent.optional_fields != empty -%}
|
||||
Optional args:
|
||||
{% for f in agent.optional_fields -%}
|
||||
- {{ f.name }} (type: {{ f.type }}): {{ f.description }}{{ "\r\n " }}
|
||||
{%- endfor %}
|
||||
{%- endif %}
|
||||
{% endfor %}
|
||||
{%- endif -%}
|
||||
{{ "\r\n" }}
|
||||
{%- endfor %}
|
||||
|
||||
[CONVERSATION]
|
||||
{{ conversation }}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,14 @@
|
|||
What is the next step based on the CONVERSATION?
|
||||
Route to the last handling agent in priority.
|
||||
{% if expected_next_action_agent != empty -%}
|
||||
Expected next action agent is {{ expected_next_action_agent }}.
|
||||
Expected next action agent is {{ expected_next_action_agent }}.
|
||||
{%- else -%}
|
||||
Next action agent is inferred based on user lastest response.
|
||||
{%- endif %}
|
||||
If user wants to speak to customer service, use function human_intervention_needed.
|
||||
{% if expected_user_goal_agent != empty -%}
|
||||
Expected user goal agent is {{ expected_user_goal_agent }}.
|
||||
{%- else -%}
|
||||
User goal agent is inferred based on user initial request.
|
||||
{%- endif %}
|
||||
If user wants to speak to customer service, use function human_intervention_needed.
|
||||
If user wants to or is processing with a specific task that can be handled by agents, respond in appropriate output format defined to let proper agent to handle the task.
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<RoleDialogModel> conversations)
|
||||
{
|
||||
if (!_convSettings.ShowVerboseLog) return;
|
||||
|
|
@ -213,6 +234,12 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", BuildContentLog(input));
|
||||
}
|
||||
|
||||
public override async Task OnStateChanged(StateChangeModel stateChange)
|
||||
{
|
||||
if (stateChange == null) return;
|
||||
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnStateChangeGenerated", BuildStateChangeLog(stateChange));
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IRoutingHook
|
||||
|
|
@ -220,9 +247,13 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
{
|
||||
var conversationId = _state.GetConversationId();
|
||||
var agent = await _agentService.LoadAgent(agentId);
|
||||
var preAgent = await _agentService.LoadAgent(preAgentId);
|
||||
|
||||
var log = $"{agent.Name} is enqueued{(reason != null ? $" ({reason})" : "")}";
|
||||
// Agent queue log
|
||||
var log = $"{agent.Name} is enqueued";
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnAgentQueueChanged", BuildAgentQueueChangedLog(conversationId, log));
|
||||
|
||||
// Content log
|
||||
log = $"{agent.Name} is enqueued{(reason != null ? $" ({reason})" : "")}";
|
||||
var message = new RoleDialogModel(AgentRole.System, log)
|
||||
{
|
||||
MessageId = _routingCtx.MessageId
|
||||
|
|
@ -243,7 +274,12 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
var agent = await _agentService.LoadAgent(agentId);
|
||||
var currentAgent = await _agentService.LoadAgent(currentAgentId);
|
||||
|
||||
var log = $"{agent.Name} is dequeued{(reason != null ? $" ({reason})" : "")}, current agent is {currentAgent?.Name}";
|
||||
// Agent queue log
|
||||
var log = $"{agent.Name} is dequeued";
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnAgentQueueChanged", BuildAgentQueueChangedLog(conversationId, log));
|
||||
|
||||
// Content log
|
||||
log = $"{agent.Name} is dequeued{(reason != null ? $" ({reason})" : "")}, current agent is {currentAgent?.Name}";
|
||||
var message = new RoleDialogModel(AgentRole.System, log)
|
||||
{
|
||||
MessageId = _routingCtx.MessageId
|
||||
|
|
@ -264,7 +300,12 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
var fromAgent = await _agentService.LoadAgent(fromAgentId);
|
||||
var toAgent = await _agentService.LoadAgent(toAgentId);
|
||||
|
||||
var log = $"{fromAgent.Name} is replaced to {toAgent.Name}{(reason != null ? $" ({reason})" : "")}";
|
||||
// Agent queue log
|
||||
var log = $"Agent queue is replaced from {fromAgent.Name} to {toAgent.Name}";
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnAgentQueueChanged", BuildAgentQueueChangedLog(conversationId, log));
|
||||
|
||||
// Content log
|
||||
log = $"{fromAgent.Name} is replaced to {toAgent.Name}{(reason != null ? $" ({reason})" : "")}";
|
||||
var message = new RoleDialogModel(AgentRole.System, log)
|
||||
{
|
||||
MessageId = _routingCtx.MessageId
|
||||
|
|
@ -282,9 +323,13 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
public async Task OnAgentQueueEmptied(string agentId, string? reason = null)
|
||||
{
|
||||
var conversationId = _state.GetConversationId();
|
||||
var agent = await _agentService.LoadAgent(agentId);
|
||||
|
||||
var log = reason ?? "Agent queue is cleared";
|
||||
// Agent queue log
|
||||
var log = $"Agent queue is empty";
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnAgentQueueChanged", BuildAgentQueueChangedLog(conversationId, log));
|
||||
|
||||
// Content log
|
||||
log = reason ?? "Agent queue is cleared";
|
||||
var message = new RoleDialogModel(AgentRole.System, log)
|
||||
{
|
||||
MessageId = _routingCtx.MessageId
|
||||
|
|
@ -320,7 +365,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
{
|
||||
var conversationId = _state.GetConversationId();
|
||||
var agent = await _agentService.LoadAgent(message.CurrentAgentId);
|
||||
var log = $"Revised user goal agent to: {agent?.Name}";
|
||||
var log = $"Revised user goal agent to {instruct.OriginalAgent}";
|
||||
|
||||
var input = new ContentLogInputModel(conversationId, message)
|
||||
{
|
||||
|
|
@ -379,4 +424,33 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
|
||||
return JsonSerializer.Serialize(log, _options.JsonSerializerOptions);
|
||||
}
|
||||
|
||||
private string BuildStateChangeLog(StateChangeModel stateChange)
|
||||
{
|
||||
var log = new StateChangeOutputModel
|
||||
{
|
||||
ConversationId = stateChange.ConversationId,
|
||||
MessageId = stateChange.MessageId,
|
||||
Name = stateChange.Name,
|
||||
BeforeValue = stateChange.BeforeValue,
|
||||
BeforeActiveRounds = stateChange.BeforeActiveRounds,
|
||||
AfterValue = stateChange.AfterValue,
|
||||
AfterActiveRounds = stateChange.AfterActiveRounds,
|
||||
CreateTime = DateTime.UtcNow
|
||||
};
|
||||
|
||||
return JsonSerializer.Serialize(log, _options.JsonSerializerOptions);
|
||||
}
|
||||
|
||||
private string BuildAgentQueueChangedLog(string conversationId, string log)
|
||||
{
|
||||
var model = new AgentQueueChangedLogModel
|
||||
{
|
||||
ConversationId = conversationId,
|
||||
Log = log,
|
||||
CreateTime = DateTime.UtcNow
|
||||
};
|
||||
|
||||
return JsonSerializer.Serialize(model, _options.JsonSerializerOptions);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ public class FunctionDefMongoElement
|
|||
{
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string? VisibilityExpression { get; set; }
|
||||
public string? Impact { get; set; }
|
||||
public FunctionParametersDefMongoElement Parameters { get; set; } = new FunctionParametersDefMongoElement();
|
||||
|
||||
|
|
@ -22,6 +23,7 @@ public class FunctionDefMongoElement
|
|||
{
|
||||
Name = function.Name,
|
||||
Description = function.Description,
|
||||
VisibilityExpression = function.VisibilityExpression,
|
||||
Impact = function.Impact,
|
||||
Parameters = new FunctionParametersDefMongoElement
|
||||
{
|
||||
|
|
@ -38,6 +40,7 @@ public class FunctionDefMongoElement
|
|||
{
|
||||
Name = mongoFunction.Name,
|
||||
Description = mongoFunction.Description,
|
||||
VisibilityExpression = mongoFunction.VisibilityExpression,
|
||||
Impact = mongoFunction.Impact,
|
||||
Parameters = new FunctionParametersDef
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Conversations.Models;
|
||||
using BotSharp.Abstraction.Evaluations.Settings;
|
||||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Repositories.Filters;
|
||||
using BotSharp.Abstraction.Routing.Models;
|
||||
using BotSharp.Abstraction.Routing.Settings;
|
||||
using BotSharp.Abstraction.Tasks.Models;
|
||||
using BotSharp.Plugin.MongoStorage.Collections;
|
||||
using BotSharp.Plugin.MongoStorage.Models;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue