Merge branch 'master' into mcp
This commit is contained in:
commit
8b467891fc
|
|
@ -1,24 +1,24 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<MSExtensionsVersion>8.0.0</MSExtensionsVersion>
|
||||
<AspNetCoreVersion>2.3.0</AspNetCoreVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="EntityFramework" Version="6.4.4" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.StaticFiles" Version="2.2.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="$(MSExtensionsVersion)" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="$(MSExtensionsVersion)" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="$(MSExtensionsVersion)" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="$(MSExtensionsVersion)" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Http.Abstractions" Version="$(AspNetCoreVersion)" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.StaticFiles" Version="$(AspNetCoreVersion)" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.3" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
|
||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageVersion Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="7.1.2" />
|
||||
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.0.0" />
|
||||
<PackageVersion Include="System.Memory.Data" Version="8.0.0" />
|
||||
<PackageVersion Include="System.Text.Json" Version="8.0.5" />
|
||||
<PackageVersion Include="Serilog" Version="2.10.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Console" Version="5.0.1" />
|
||||
<PackageVersion Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageVersion Include="Rougamo.Fody" Version="4.0.4" />
|
||||
<PackageVersion Include="Aspects.Cache" Version="2.0.4" />
|
||||
<PackageVersion Include="DistributedLock.Redis" Version="1.0.3" />
|
||||
|
|
@ -33,6 +33,24 @@
|
|||
<PackageVersion Include="NAudio.Core" Version="2.2.1" />
|
||||
<PackageVersion Include="Whisper.net" Version="1.5.0" />
|
||||
<PackageVersion Include="Whisper.net.Runtime" Version="1.5.0" />
|
||||
<PackageVersion Include="NCrontab" Version="3.3.3" />
|
||||
<PackageVersion Include="Azure.AI.OpenAI" Version="2.1.0" />
|
||||
<PackageVersion Include="OpenAI" Version="2.1.0" />
|
||||
<PackageVersion Include="MailKit" Version="4.7.0" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.8" />
|
||||
<PackageVersion Include="MySql.Data" Version="9.0.0" />
|
||||
<PackageVersion Include="NPOI" Version="2.7.1" />
|
||||
<PackageVersion Include="LLMSharp.Google.Palm" Version="1.0.2" />
|
||||
<PackageVersion Include="Mscc.GenerativeAI" Version="2.0.1" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
|
||||
<PackageVersion Include="Refit.HttpClientFactory" Version="7.0.0" />
|
||||
<PackageVersion Include="Jint" Version="4.1.0" />
|
||||
<PackageVersion Include="PdfPig" Version="0.1.8" />
|
||||
<PackageVersion Include="TensorFlow.Keras" Version="0.15.0" />
|
||||
<PackageVersion Include="LangChain.Providers.Google.VertexAI" Version="0.15.3-dev.58" />
|
||||
<PackageVersion Include="LLamaSharp" Version="0.20.0" />
|
||||
<PackageVersion Include="FaissMask" Version="0.2.0" />
|
||||
<PackageVersion Include="FastText.NetWrapper" Version="1.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
||||
|
|
|
|||
|
|
@ -58,17 +58,13 @@ public class DialogElement
|
|||
[JsonPropertyName("payload")]
|
||||
public string? Payload { get; set; }
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
[JsonPropertyName("data")]
|
||||
public object? Data { get; set; }
|
||||
|
||||
public DialogElement()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public DialogElement(DialogMetaData meta, string content, string? richContent = null,
|
||||
string? secondaryContent = null, string? secondaryRichContent = null, string? payload = null, object? data = null)
|
||||
string? secondaryContent = null, string? secondaryRichContent = null, string? payload = null)
|
||||
{
|
||||
MetaData = meta;
|
||||
Content = content;
|
||||
|
|
@ -76,7 +72,6 @@ public class DialogElement
|
|||
SecondaryContent = secondaryContent;
|
||||
SecondaryRichContent = secondaryRichContent;
|
||||
Payload = payload;
|
||||
Data = data;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
|
|
|
|||
|
|
@ -8,8 +8,12 @@ public class StateConst
|
|||
public const string NEXT_ACTION_REASON = "next_action_reason";
|
||||
public const string USER_GOAL_AGENT = "user_goal_agent";
|
||||
public const string AGENT_REDIRECTION_REASON = "agent_redirection_reason";
|
||||
// lazy or eager
|
||||
public const string ROUTING_MODE = "routing_mode";
|
||||
public const string LAZY_ROUTING_AGENT_ID = "lazy_routing_agent_id";
|
||||
|
||||
public const string LANGUAGE = "language";
|
||||
|
||||
public const string SUB_CONVERSATION_ID = "sub_conversation_id";
|
||||
public const string ORIGIN_CONVERSATION_ID = "origin_conversation_id";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using BotSharp.Abstraction.Functions.Models;
|
||||
|
||||
namespace BotSharp.Abstraction.Loggers;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -37,4 +39,13 @@ public interface IContentGeneratingHook
|
|||
/// <param name="content"></param>
|
||||
/// <returns></returns>
|
||||
Task OnRenderingTemplate(Agent agent, string name, string content) => Task.CompletedTask;
|
||||
|
||||
/// <summary>
|
||||
/// Realtime session updated
|
||||
/// </summary>
|
||||
/// <param name="agent"></param>
|
||||
/// <param name="instruction"></param>
|
||||
/// <param name="functions"></param>
|
||||
/// <returns></returns>
|
||||
Task OnSessionUpdated(Agent agent, string instruction, FunctionDef[] functions) => Task.CompletedTask;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,9 +24,11 @@ public interface IRealTimeCompletion
|
|||
Task Disconnect();
|
||||
|
||||
Task<RealtimeSession> CreateSession(Agent agent, List<RoleDialogModel> conversations);
|
||||
Task UpdateInitialSession(RealtimeHubConnection conn);
|
||||
Task UpdateSession(RealtimeHubConnection conn);
|
||||
Task InsertConversationItem(RoleDialogModel message);
|
||||
Task RemoveConversationItem(string itemId);
|
||||
Task TriggerModelInference(string? instructions = null);
|
||||
Task CancelModelResponse();
|
||||
Task<List<RoleDialogModel>> OnResponsedDone(RealtimeHubConnection conn, string response);
|
||||
Task<RoleDialogModel> OnConversationItemCreated(RealtimeHubConnection conn, string response);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ namespace BotSharp.Abstraction.Options;
|
|||
|
||||
public class BotSharpOptions
|
||||
{
|
||||
private readonly static JsonSerializerOptions defaultJsonOptions = new JsonSerializerOptions()
|
||||
public readonly static JsonSerializerOptions defaultJsonOptions = new JsonSerializerOptions()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ public class RealtimeHubConnection
|
|||
{
|
||||
public string Event { get; set; } = null!;
|
||||
public string StreamId { get; set; } = null!;
|
||||
public string EntryAgentId { get; set; } = null!;
|
||||
public string CurrentAgentId { get; set; } = null!;
|
||||
public string ConversationId { get; set; } = null!;
|
||||
public string Data { get; set; } = string.Empty;
|
||||
public string Model { get; set; } = null!;
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@ public interface IRoutingContext
|
|||
bool IsEmpty { get; }
|
||||
string IntentName { get; set; }
|
||||
int AgentCount { get; }
|
||||
void Push(string agentId, string? reason = null);
|
||||
void Pop(string? reason = null);
|
||||
void PopTo(string agentId, string reason);
|
||||
void Replace(string agentId, string? reason = null);
|
||||
void Push(string agentId, string? reason = null, bool updateLazyRouting = true);
|
||||
void Pop(string? reason = null, bool updateLazyRouting = true);
|
||||
void PopTo(string agentId, string reason, bool updateLazyRouting = true);
|
||||
void Replace(string agentId, string? reason = null, bool updateLazyRouting = true);
|
||||
void Empty(string? reason = null);
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
namespace BotSharp.Abstraction.Routing.Models;
|
||||
|
||||
public class FallbackArgs
|
||||
{
|
||||
[JsonPropertyName("fallback_reason")]
|
||||
public string Reason { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("user_question")]
|
||||
public string Question { get; set; } = null;
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
<LangVersion>$(LangVersion)</LangVersion>
|
||||
<VersionPrefix>$(BotSharpVersion)</VersionPrefix>
|
||||
<GeneratePackageOnBuild>$(GeneratePackageOnBuild)</GeneratePackageOnBuild>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
<OutputPath>$(SolutionDir)packages</OutputPath>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
|
@ -29,7 +30,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NCrontab" Version="3.3.3" />
|
||||
<PackageReference Include="NCrontab" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>$(TargetFramework)</TargetFramework>
|
||||
|
|
@ -66,6 +66,8 @@
|
|||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\reasoner.sequential.get_remaining_task.liquid" />
|
||||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\reasoner.sequential.liquid" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\agent.json" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-routing-fallback_to_router.json" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-routing-redirect_to_agent.json" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\instructions\instruction.liquid" />
|
||||
<None Remove="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\agent.json" />
|
||||
<None Remove="data\agents\01dcc3e5-0af7-49e6-ad7a-a760bd12dc4b\functions.json" />
|
||||
|
|
@ -82,6 +84,7 @@
|
|||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\response_with_function.liquid" />
|
||||
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\translation_prompt.liquid" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\select_file_prompt.liquid" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-routing-fallback_to_router.fn.liquid" />
|
||||
<None Remove="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\agent.json" />
|
||||
<None Remove="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\instructions\instruction.liquid" />
|
||||
<None Remove="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\templates\instruction.executor.liquid" />
|
||||
|
|
@ -146,6 +149,15 @@
|
|||
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\translation_prompt.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-routing-fallback_to_router.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\functions\route_to_agent.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-routing-fallback_to_router.fn.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\dfd9b46d-d00c-40af-8a75-3fbdc2b89869\agent.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using BotSharp.Abstraction.Infrastructures.Enums;
|
||||
using BotSharp.Abstraction.Messaging;
|
||||
using BotSharp.Abstraction.Messaging.Models.RichContent;
|
||||
using BotSharp.Abstraction.Routing.Settings;
|
||||
|
|
@ -36,7 +37,17 @@ public partial class ConversationService
|
|||
// Enqueue receiving agent first in case it stop completion by OnMessageReceived
|
||||
var routing = _services.GetRequiredService<IRoutingService>();
|
||||
routing.Context.SetMessageId(_conversationId, message.MessageId);
|
||||
routing.Context.Push(agent.Id, reason: "request started");
|
||||
|
||||
// Check the routing mode
|
||||
var states = _services.GetRequiredService<IConversationStateService>();
|
||||
var routingMode = states.GetState(StateConst.ROUTING_MODE, "hard");
|
||||
routing.Context.Push(agent.Id, reason: "request started", updateLazyRouting: false);
|
||||
|
||||
if (routingMode == "lazy")
|
||||
{
|
||||
message.CurrentAgentId = states.GetState(StateConst.LAZY_ROUTING_AGENT_ID, message.CurrentAgentId);
|
||||
routing.Context.Push(message.CurrentAgentId, reason: "lazy routing", updateLazyRouting: false);
|
||||
}
|
||||
|
||||
// Save payload in order to assign the payload before hook is invoked
|
||||
if (replyMessage != null && !string.IsNullOrEmpty(replyMessage.Payload))
|
||||
|
|
@ -77,7 +88,7 @@ public partial class ConversationService
|
|||
{
|
||||
agent = await agentService.LoadAgent(message.CurrentAgentId);
|
||||
}
|
||||
|
||||
|
||||
if (agent.Type == AgentType.Routing)
|
||||
{
|
||||
response = await routing.InstructLoop(message, dialogs);
|
||||
|
|
|
|||
|
|
@ -220,7 +220,6 @@ public class ConversationStateService : IConversationStateService
|
|||
{
|
||||
if (_conversationId == null || _sidecar?.IsEnabled() == true)
|
||||
{
|
||||
Reset();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -253,7 +252,6 @@ public class ConversationStateService : IConversationStateService
|
|||
}
|
||||
|
||||
_db.UpdateConversationStates(_conversationId, states);
|
||||
Reset();
|
||||
_logger.LogInformation($"Saved states of conversation {_conversationId}");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -52,8 +52,7 @@ public class ConversationStorage : IConversationStorage
|
|||
MetaData = meta,
|
||||
Content = dialog.Content,
|
||||
SecondaryContent = dialog.SecondaryContent,
|
||||
Payload = dialog.Payload,
|
||||
Data = dialog.Data
|
||||
Payload = dialog.Payload
|
||||
});
|
||||
}
|
||||
else
|
||||
|
|
@ -84,8 +83,7 @@ public class ConversationStorage : IConversationStorage
|
|||
SecondaryContent = dialog.SecondaryContent,
|
||||
RichContent = richContent,
|
||||
SecondaryRichContent = secondaryRichContent,
|
||||
Payload = dialog.Payload,
|
||||
Data = dialog.Data
|
||||
Payload = dialog.Payload
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -123,8 +121,7 @@ public class ConversationStorage : IConversationStorage
|
|||
MetaData = meta,
|
||||
Content = dialog.Content,
|
||||
SecondaryContent = dialog.SecondaryContent,
|
||||
Payload = dialog.Payload,
|
||||
Data = dialog.Data
|
||||
Payload = dialog.Payload
|
||||
});
|
||||
}
|
||||
else
|
||||
|
|
@ -155,8 +152,7 @@ public class ConversationStorage : IConversationStorage
|
|||
SecondaryContent = dialog.SecondaryContent,
|
||||
RichContent = richContent,
|
||||
SecondaryRichContent = secondaryRichContent,
|
||||
Payload = dialog.Payload,
|
||||
Data = dialog.Data
|
||||
Payload = dialog.Payload
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -200,8 +196,7 @@ public class ConversationStorage : IConversationStorage
|
|||
RichContent = richContent,
|
||||
SecondaryContent = secondaryContent,
|
||||
SecondaryRichContent = secondaryRichContent,
|
||||
Payload = payload,
|
||||
Data = dialog.Data
|
||||
Payload = payload
|
||||
};
|
||||
results.Add(record);
|
||||
|
||||
|
|
|
|||
|
|
@ -60,12 +60,15 @@ public class TokenStatistics : ITokenStatistics
|
|||
stat.SetState("llm_total_cost", total_cost, isNeedVersion: false, source: StateSource.Application);
|
||||
|
||||
// Save stats
|
||||
var metric = StatsMetric.AgentLlmCost;
|
||||
var dim = "agent";
|
||||
var agentId = message.CurrentAgentId ?? string.Empty;
|
||||
var globalStats = _services.GetRequiredService<IBotSharpStatsService>();
|
||||
var body = new BotSharpStatsInput
|
||||
{
|
||||
Metric = StatsMetric.AgentLlmCost,
|
||||
Dimension = "agent",
|
||||
DimRefVal = message.CurrentAgentId,
|
||||
Metric = metric,
|
||||
Dimension = dim,
|
||||
DimRefVal = agentId,
|
||||
RecordTime = DateTime.UtcNow,
|
||||
IntervalType = StatsInterval.Day,
|
||||
Data = [
|
||||
|
|
@ -75,7 +78,7 @@ public class TokenStatistics : ITokenStatistics
|
|||
new StatsKeyValuePair("completion_cost_total", deltaCompletionCost)
|
||||
]
|
||||
};
|
||||
globalStats.UpdateStats("global-llm-cost", body);
|
||||
globalStats.UpdateStats($"global-{metric}-{dim}-{agentId}", body);
|
||||
}
|
||||
|
||||
public void PrintStatistics()
|
||||
|
|
@ -110,6 +113,10 @@ public class TokenStatistics : ITokenStatistics
|
|||
|
||||
public void StopTimer()
|
||||
{
|
||||
if (_timer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_timer.Stop();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
using BotSharp.Abstraction.Realtime;
|
||||
using System.Net.WebSockets;
|
||||
using System;
|
||||
using BotSharp.Abstraction.Realtime.Models;
|
||||
using BotSharp.Abstraction.MLTasks;
|
||||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Conversations.Enums;
|
||||
using BotSharp.Abstraction.Routing.Models;
|
||||
|
||||
namespace BotSharp.Core.Realtime;
|
||||
|
||||
|
|
@ -11,6 +11,7 @@ public class RealtimeHub : IRealtimeHub
|
|||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public RealtimeHub(IServiceProvider services, ILogger<RealtimeHub> logger)
|
||||
{
|
||||
_services = services;
|
||||
|
|
@ -71,18 +72,23 @@ public class RealtimeHub : IRealtimeHub
|
|||
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.LoadAgent(conversation.AgentId);
|
||||
conn.EntryAgentId = agent.Id;
|
||||
conn.CurrentAgentId = agent.Id;
|
||||
|
||||
var routing = _services.GetRequiredService<IRoutingService>();
|
||||
routing.Context.Push(agent.Id);
|
||||
|
||||
var dialogs = convService.GetDialogHistory();
|
||||
if (dialogs.Count == 0)
|
||||
{
|
||||
dialogs.Add(new RoleDialogModel(AgentRole.User, "Hi"));
|
||||
}
|
||||
routing.Context.SetDialogs(dialogs);
|
||||
|
||||
await completer.Connect(conn,
|
||||
onModelReady: async () =>
|
||||
{
|
||||
// Control initial session
|
||||
await completer.UpdateInitialSession(conn);
|
||||
|
||||
await completer.UpdateSession(conn);
|
||||
|
||||
// Add dialog history
|
||||
foreach (var item in dialogs)
|
||||
|
|
@ -92,7 +98,7 @@ public class RealtimeHub : IRealtimeHub
|
|||
|
||||
if (dialogs.LastOrDefault()?.Role == AgentRole.Assistant)
|
||||
{
|
||||
// await completer.TriggerModelInference($"Rephase your last response:\r\n{dialogs.LastOrDefault()?.Content}");
|
||||
await completer.TriggerModelInference($"Rephase your last response:\r\n{dialogs.LastOrDefault()?.Content}");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -118,16 +124,40 @@ public class RealtimeHub : IRealtimeHub
|
|||
foreach (var message in messages)
|
||||
{
|
||||
// Invoke function
|
||||
if (message.MessageType == "function_call")
|
||||
if (message.MessageType == MessageTypeName.FunctionCall)
|
||||
{
|
||||
await routing.InvokeFunction(message.FunctionName, message);
|
||||
message.Role = AgentRole.Function;
|
||||
await completer.InsertConversationItem(message);
|
||||
await completer.TriggerModelInference("Reply based on the function's output.");
|
||||
|
||||
if (message.FunctionName == "route_to_agent")
|
||||
{
|
||||
var inst = JsonSerializer.Deserialize<RoutingArgs>(message.FunctionArgs ?? "{}");
|
||||
message.Content = $"Connected to agent of {inst.AgentName}";
|
||||
conn.CurrentAgentId = routing.Context.GetCurrentAgentId();
|
||||
|
||||
await completer.UpdateSession(conn);
|
||||
await completer.InsertConversationItem(message);
|
||||
await completer.TriggerModelInference($"Guide the user through the next steps of the process as this Agent ({inst.AgentName}), following its instructions and operational procedures.");
|
||||
}
|
||||
else if (message.FunctionName == "util-routing-fallback_to_router")
|
||||
{
|
||||
var inst = JsonSerializer.Deserialize<FallbackArgs>(message.FunctionArgs ?? "{}");
|
||||
message.Content = $"Returned to Router due to {inst.Reason}";
|
||||
conn.CurrentAgentId = routing.Context.GetCurrentAgentId();
|
||||
|
||||
await completer.UpdateSession(conn);
|
||||
await completer.InsertConversationItem(message);
|
||||
await completer.TriggerModelInference($"Check with user whether to proceed the new request: {inst.Reason}");
|
||||
}
|
||||
else
|
||||
{
|
||||
await completer.InsertConversationItem(message);
|
||||
await completer.TriggerModelInference("Reply based on the function's output.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// append transcript to conversation
|
||||
// append output audio transcript to conversation
|
||||
storage.Append(conn.ConversationId, message);
|
||||
dialogs.Add(message);
|
||||
|
||||
|
|
@ -136,10 +166,7 @@ public class RealtimeHub : IRealtimeHub
|
|||
hook.SetAgent(agent)
|
||||
.SetConversation(conversation);
|
||||
|
||||
if (!string.IsNullOrEmpty(message.Content))
|
||||
{
|
||||
await hook.OnMessageReceived(message);
|
||||
}
|
||||
await hook.OnResponseGenerated(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -150,9 +177,17 @@ public class RealtimeHub : IRealtimeHub
|
|||
},
|
||||
onInputAudioTranscriptionCompleted: async message =>
|
||||
{
|
||||
// append transcript to conversation
|
||||
// append input audio transcript to conversation
|
||||
storage.Append(conn.ConversationId, message);
|
||||
dialogs.Add(message);
|
||||
|
||||
foreach (var hook in hookProvider.HooksOrderByPriority)
|
||||
{
|
||||
hook.SetAgent(agent)
|
||||
.SetConversation(conversation);
|
||||
|
||||
await hook.OnMessageReceived(message);
|
||||
}
|
||||
},
|
||||
onUserInterrupted: async () =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -500,16 +500,6 @@ public partial class FileRepository
|
|||
batchSize = batchLimit;
|
||||
}
|
||||
|
||||
if (bufferHours <= 0)
|
||||
{
|
||||
bufferHours = 12;
|
||||
}
|
||||
|
||||
if (messageLimit <= 0)
|
||||
{
|
||||
messageLimit = 2;
|
||||
}
|
||||
|
||||
foreach (var d in Directory.GetDirectories(dir))
|
||||
{
|
||||
var convFile = Path.Combine(d, CONVERSATION_FILE);
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@ namespace BotSharp.Core.Routing.Functions;
|
|||
|
||||
public class FallbackToRouterFn : IFunctionCallback
|
||||
{
|
||||
public string Name => "fallback_to_router";
|
||||
public string Name => "util-routing-fallback_to_router";
|
||||
private readonly IServiceProvider _services;
|
||||
|
||||
public FallbackToRouterFn(IServiceProvider services)
|
||||
{
|
||||
_services = services;
|
||||
|
|
@ -14,30 +15,10 @@ public class FallbackToRouterFn : IFunctionCallback
|
|||
|
||||
public async Task<bool> Execute(RoleDialogModel message)
|
||||
{
|
||||
var args = JsonSerializer.Deserialize<RoutingArgs>(message.FunctionArgs);
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agents = await agentService.GetAgents(new AgentFilter
|
||||
{
|
||||
AgentNames = [args.AgentName]
|
||||
});
|
||||
var targetAgent = agents.Items.FirstOrDefault();
|
||||
if (targetAgent == null)
|
||||
{
|
||||
message.Content = $"Can't find routing agent {args.AgentName}";
|
||||
return false;
|
||||
}
|
||||
|
||||
var conv = _services.GetRequiredService<IConversationService>();
|
||||
var dialogs = conv.GetDialogHistory();
|
||||
|
||||
var args = JsonSerializer.Deserialize<FallbackArgs>(message.FunctionArgs);
|
||||
var routing = _services.GetRequiredService<IRoutingService>();
|
||||
routing.Context.Replace(targetAgent.Id);
|
||||
message.CurrentAgentId = targetAgent.Id;
|
||||
|
||||
var response = await routing.InstructLoop(message, dialogs);
|
||||
|
||||
message.Content = response.Content;
|
||||
message.StopCompletion = true;
|
||||
routing.Context.PopTo(routing.Context.EntryAgentId, "pop to entry agent");
|
||||
message.Content = args.Question;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
namespace BotSharp.Core.Routing.Hooks;
|
||||
|
||||
public class RoutingUtilityHook : IAgentUtilityHook
|
||||
{
|
||||
private static string PREFIX = "util-routing-";
|
||||
private static string REDIRECT_TO_AGENT = $"{PREFIX}redirect_to_agent";
|
||||
private static string FALLBACK_TO_ROUTER = $"{PREFIX}fallback_to_router";
|
||||
|
||||
public void AddUtilities(List<AgentUtility> utilities)
|
||||
{
|
||||
var utility = new AgentUtility
|
||||
{
|
||||
Name = "routing.tools",
|
||||
Functions = [new($"{REDIRECT_TO_AGENT}"), new($"{FALLBACK_TO_ROUTER}")],
|
||||
Templates = [new($"{REDIRECT_TO_AGENT}.fn"), new($"{FALLBACK_TO_ROUTER}.fn")]
|
||||
};
|
||||
|
||||
utilities.Add(utility);
|
||||
}
|
||||
}
|
||||
|
|
@ -73,7 +73,7 @@ public class NaiveReasoner : IRoutingReasoner
|
|||
};
|
||||
var response = await completion.GetChatCompletions(router, dialogs);
|
||||
|
||||
inst = response.Content.JsonContent<FunctionCallFromLlm>();
|
||||
inst = (response.FunctionArgs ?? response.Content).JsonContent<FunctionCallFromLlm>();
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using BotSharp.Abstraction.Infrastructures.Enums;
|
||||
using BotSharp.Abstraction.Routing.Settings;
|
||||
|
||||
namespace BotSharp.Core.Routing;
|
||||
|
|
@ -79,7 +80,7 @@ public class RoutingContext : IRoutingContext
|
|||
/// </summary>
|
||||
/// <param name="agentId">Id or Name</param>
|
||||
/// <param name="reason"></param>
|
||||
public void Push(string agentId, string? reason = null)
|
||||
public void Push(string agentId, string? reason = null, bool updateLazyRouting = true)
|
||||
{
|
||||
// Convert id to name
|
||||
if (!Guid.TryParse(agentId, out _))
|
||||
|
|
@ -99,13 +100,15 @@ public class RoutingContext : IRoutingContext
|
|||
HookEmitter.Emit<IRoutingHook>(_services, async hook =>
|
||||
await hook.OnAgentEnqueued(agentId, preAgentId, reason: reason)
|
||||
).Wait();
|
||||
|
||||
UpdateLazyRoutingAgent(updateLazyRouting);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pop current agent
|
||||
/// </summary>
|
||||
public void Pop(string? reason = null)
|
||||
public void Pop(string? reason = null, bool updateLazyRouting = true)
|
||||
{
|
||||
if (_stack.Count == 0)
|
||||
{
|
||||
|
|
@ -149,15 +152,17 @@ public class RoutingContext : IRoutingContext
|
|||
_stack.Push(agentId);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateLazyRoutingAgent(updateLazyRouting);
|
||||
}
|
||||
|
||||
public void PopTo(string agentId, string reason)
|
||||
public void PopTo(string agentId, string reason, bool updateLazyRouting = true)
|
||||
{
|
||||
var currentAgentId = GetCurrentAgentId();
|
||||
while (!string.IsNullOrEmpty(currentAgentId) &&
|
||||
currentAgentId != agentId)
|
||||
{
|
||||
Pop(reason);
|
||||
Pop(reason, updateLazyRouting: updateLazyRouting);
|
||||
currentAgentId = GetCurrentAgentId();
|
||||
}
|
||||
}
|
||||
|
|
@ -181,7 +186,7 @@ public class RoutingContext : IRoutingContext
|
|||
return _stack.ToArray().Contains(agentId);
|
||||
}
|
||||
|
||||
public void Replace(string agentId, string? reason = null)
|
||||
public void Replace(string agentId, string? reason = null, bool updateLazyRouting = true)
|
||||
{
|
||||
var fromAgent = agentId;
|
||||
var toAgent = agentId;
|
||||
|
|
@ -200,6 +205,8 @@ public class RoutingContext : IRoutingContext
|
|||
await hook.OnAgentReplaced(fromAgent, toAgent, reason: reason)
|
||||
).Wait();
|
||||
}
|
||||
|
||||
UpdateLazyRoutingAgent(updateLazyRouting);
|
||||
}
|
||||
|
||||
public void Empty(string? reason = null)
|
||||
|
|
@ -275,4 +282,24 @@ public class RoutingContext : IRoutingContext
|
|||
{
|
||||
_dialogs = [];
|
||||
}
|
||||
|
||||
private void UpdateLazyRoutingAgent(bool updateLazyRouting)
|
||||
{
|
||||
if (!updateLazyRouting)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Set next handling agent for lazy routing mode
|
||||
var states = _services.GetRequiredService<IConversationStateService>();
|
||||
var routingMode = states.GetState(StateConst.ROUTING_MODE, "hard");
|
||||
if (routingMode == "lazy")
|
||||
{
|
||||
var agentId = GetCurrentAgentId();
|
||||
if (agentId != BuiltInAgentId.Fallback)
|
||||
{
|
||||
states.SetState(StateConst.LAZY_ROUTING_AGENT_ID, agentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,5 +37,7 @@ public class RoutingPlugin : IBotSharpPlugin
|
|||
services.AddScoped<IRoutingReasoner, HFReasoner>();
|
||||
|
||||
services.AddScoped<IRoutingReasoner, OneStepForwardReasoner>();
|
||||
|
||||
services.AddScoped<IAgentUtilityHook, RoutingUtilityHook>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ public partial class RoutingService
|
|||
// Handle output routing exception.
|
||||
if (agent.Type == AgentType.Routing)
|
||||
{
|
||||
// Forgot about what situation needs to handle in this way
|
||||
response.Content = "Apologies, I'm not quite sure I understand. Could you please provide additional clarification or context?";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "01fcc3e5-0af7-49e6-ad7a-a760bd12dc4d",
|
||||
"name": "Fallback Agent",
|
||||
"description": "Don't have sufficient confidence to trigger any of existing agent.",
|
||||
"description": "Handle initiated conversation without specific task given yet or don't have sufficient confidence to handle user task.",
|
||||
"type": "task",
|
||||
"createdDateTime": "2024-05-07T10:00:00Z",
|
||||
"updatedDateTime": "2024-05-07T10:00:00Z",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"name": "route_to_agent",
|
||||
"description": "Route request to appropriate AI agent.",
|
||||
"visibility_expression": "{% if states.routing_mode == 'lazy' %}visible{% endif %}",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"next_action_agent": {
|
||||
"type": "string",
|
||||
"description": "Agent for next action based on user latest response"
|
||||
},
|
||||
"next_action_reason": {
|
||||
"type": "string",
|
||||
"description": "The reason why route to this agent."
|
||||
},
|
||||
"user_goal_agent": {
|
||||
"type": "string",
|
||||
"description": "Agent who can acheive user initial task."
|
||||
},
|
||||
"conversation_end": {
|
||||
"type": "boolean",
|
||||
"description": "User is ending the conversation."
|
||||
},
|
||||
"args": {
|
||||
"type": "object",
|
||||
"description": "Required parameters of next action agent"
|
||||
}
|
||||
},
|
||||
"required": [ "next_action_agent", "user_goal_agent", "next_action_reason", "args" ]
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,9 @@ Follow these steps to handle user request:
|
|||
2. Determine which agent is suitable to handle this conversation. Try to minimize the routing of human service.
|
||||
3. Extract and populate agent required arguments, think carefully, leave it as blank object if user didn't provide the specific arguments.
|
||||
4. You must include all required args for the selected agent, but you must not make up any parameters when there is no exact value provided, those parameters must set value as null if not declared.
|
||||
{% if routing_mode != 'lazy' %}
|
||||
5. Response must be in JSON format.
|
||||
{% endif %}
|
||||
|
||||
{% if routing_requirements and routing_requirements != empty %}
|
||||
[REQUIREMENTS]
|
||||
|
|
@ -14,6 +16,7 @@ Follow these steps to handle user request:
|
|||
{%- endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if routing_mode != 'lazy' %}
|
||||
[FUNCTIONS]
|
||||
{% for handler in routing_handlers -%}
|
||||
# {{ handler.description}}
|
||||
|
|
@ -26,6 +29,7 @@ Parameters:
|
|||
{%- endif %}
|
||||
{{ "\r\n" }}
|
||||
{%- endfor %}
|
||||
{% endif %}
|
||||
|
||||
[AGENTS]
|
||||
{% for agent in routing_agents -%}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "util-routing-fallback_to_router",
|
||||
"description": "Return to the Router to find the appropriate agent who can handle the user's request.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"fallback_reason": {
|
||||
"type": "string",
|
||||
"description": "The reason why you need to reach out to other agent."
|
||||
},
|
||||
"user_question": {
|
||||
"type": "string",
|
||||
"description": "User question or statement."
|
||||
}
|
||||
},
|
||||
"required": [ "fallback_reason" ]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
Carefully consider whether the current user request is related to your responsibilities. Only when it is not relevant should you consider Return to the Router.
|
||||
|
|
@ -25,17 +25,20 @@ public class GlobalStatsConversationHook : IContentGeneratingHook
|
|||
// record agent call
|
||||
var globalStats = _services.GetRequiredService<IBotSharpStatsService>();
|
||||
|
||||
var metric = StatsMetric.AgentCall;
|
||||
var dim = "agent";
|
||||
var agentId = message.CurrentAgentId ?? string.Empty;
|
||||
var body = new BotSharpStatsInput
|
||||
{
|
||||
Metric = StatsMetric.AgentCall,
|
||||
Dimension = "agent",
|
||||
DimRefVal = message.CurrentAgentId ?? string.Empty,
|
||||
Metric = metric,
|
||||
Dimension = dim,
|
||||
DimRefVal = agentId,
|
||||
RecordTime = DateTime.UtcNow,
|
||||
IntervalType = StatsInterval.Day,
|
||||
Data = [
|
||||
new StatsKeyValuePair("agent_call_count", 1)
|
||||
]
|
||||
};
|
||||
globalStats.UpdateStats("global-agent-call", body);
|
||||
globalStats.UpdateStats($"global-{metric}-{dim}-{agentId}", body);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,12 @@
|
|||
<GeneratePackageOnBuild>$(GeneratePackageOnBuild)</GeneratePackageOnBuild>
|
||||
<GenerateDocumentationFile>$(GenerateDocumentationFile)</GenerateDocumentationFile>
|
||||
<OutputPath>$(SolutionDir)packages</OutputPath>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" Version="2.1.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ public class ChatHubConversationHook : ConversationHookBase
|
|||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly IHubContext<SignalRHub> _chatHub;
|
||||
private readonly ILogger<ChatHubConversationHook> _logger;
|
||||
private readonly IUserIdentity _user;
|
||||
private readonly BotSharpOptions _options;
|
||||
private readonly ChatHubSettings _settings;
|
||||
|
|
@ -23,12 +24,14 @@ public class ChatHubConversationHook : ConversationHookBase
|
|||
public ChatHubConversationHook(
|
||||
IServiceProvider services,
|
||||
IHubContext<SignalRHub> chatHub,
|
||||
ILogger<ChatHubConversationHook> logger,
|
||||
BotSharpOptions options,
|
||||
ChatHubSettings settings,
|
||||
IUserIdentity user)
|
||||
{
|
||||
_services = services;
|
||||
_chatHub = chatHub;
|
||||
_logger = logger;
|
||||
_user = user;
|
||||
_options = options;
|
||||
_settings = settings;
|
||||
|
|
@ -177,74 +180,122 @@ public class ChatHubConversationHook : ConversationHookBase
|
|||
|
||||
private async Task InitClientConversation(string conversationId, ConversationViewModel conversation)
|
||||
{
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
try
|
||||
{
|
||||
await _chatHub.Clients.Group(conversationId).SendAsync(INIT_CLIENT_CONVERSATION, conversation);
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
{
|
||||
await _chatHub.Clients.Group(conversationId).SendAsync(INIT_CLIENT_CONVERSATION, conversation);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(INIT_CLIENT_CONVERSATION, conversation);
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(INIT_CLIENT_CONVERSATION, conversation);
|
||||
_logger.LogWarning($"Failed to init client conversation in {nameof(ChatHubConversationHook)} (conversation id: {conversationId})" +
|
||||
$"\r\n{ex.Message}\r\n{ex.InnerException}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReceiveClientMessage(string conversationId, ChatResponseModel model)
|
||||
{
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
try
|
||||
{
|
||||
await _chatHub.Clients.Group(conversationId).SendAsync(RECEIVE_CLIENT_MESSAGE, model);
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
{
|
||||
await _chatHub.Clients.Group(conversationId).SendAsync(RECEIVE_CLIENT_MESSAGE, model);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(RECEIVE_CLIENT_MESSAGE, model);
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(RECEIVE_CLIENT_MESSAGE, model);
|
||||
_logger.LogWarning($"Failed to receive assistant message in {nameof(ChatHubConversationHook)} (conversation id: {conversationId})" +
|
||||
$"\r\n{ex.Message}\r\n{ex.InnerException}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReceiveAssistantMessage(string conversationId, string? json)
|
||||
{
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
try
|
||||
{
|
||||
await _chatHub.Clients.Group(conversationId).SendAsync(RECEIVE_ASSISTANT_MESSAGE, json);
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
{
|
||||
await _chatHub.Clients.Group(conversationId).SendAsync(RECEIVE_ASSISTANT_MESSAGE, json);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(RECEIVE_ASSISTANT_MESSAGE, json);
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(RECEIVE_ASSISTANT_MESSAGE, json);
|
||||
_logger.LogWarning($"Failed to receive assistant message in {nameof(ChatHubConversationHook)} (conversation id: {conversationId})" +
|
||||
$"\r\n{ex.Message}\r\n{ex.InnerException}");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private async Task GenerateSenderAction(string conversationId, ConversationSenderActionModel action)
|
||||
{
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
try
|
||||
{
|
||||
await _chatHub.Clients.Group(conversationId).SendAsync(GENERATE_SENDER_ACTION, action);
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
{
|
||||
await _chatHub.Clients.Group(conversationId).SendAsync(GENERATE_SENDER_ACTION, action);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(GENERATE_SENDER_ACTION, action);
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(GENERATE_SENDER_ACTION, action);
|
||||
_logger.LogWarning($"Failed to generate sender action in {nameof(ChatHubConversationHook)} (conversation id: {conversationId})" +
|
||||
$"\r\n{ex.Message}\r\n{ex.InnerException}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteMessage(string conversationId, ChatResponseModel model)
|
||||
{
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
try
|
||||
{
|
||||
await _chatHub.Clients.Group(conversationId).SendAsync(DELETE_MESSAGE, model);
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
{
|
||||
await _chatHub.Clients.Group(conversationId).SendAsync(DELETE_MESSAGE, model);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(DELETE_MESSAGE, model);
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(DELETE_MESSAGE, model);
|
||||
_logger.LogWarning($"Failed to delete message in {nameof(ChatHubConversationHook)} (conversation id: {conversationId})" +
|
||||
$"\r\n{ex.Message}\r\n{ex.InnerException}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task GenerateNotification(string conversationId, string? json)
|
||||
{
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
try
|
||||
{
|
||||
await _chatHub.Clients.Group(conversationId).SendAsync(GENERATE_NOTIFICATION, json);
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
{
|
||||
await _chatHub.Clients.Group(conversationId).SendAsync(GENERATE_NOTIFICATION, json);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(GENERATE_NOTIFICATION, json);
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(GENERATE_NOTIFICATION, json);
|
||||
_logger.LogWarning($"Failed to generate notification in {nameof(ChatHubConversationHook)} (conversation id: {conversationId})" +
|
||||
$"\r\n{ex.Message}\r\n{ex.InnerException}");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ public class ChatHubCrontabHook : ICrontabHook
|
|||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly IHubContext<SignalRHub> _chatHub;
|
||||
private readonly ILogger<ChatHubCrontabHook> _logger;
|
||||
private readonly IUserIdentity _user;
|
||||
private readonly IConversationStorage _storage;
|
||||
private readonly BotSharpOptions _options;
|
||||
|
|
@ -19,6 +20,7 @@ public class ChatHubCrontabHook : ICrontabHook
|
|||
|
||||
public ChatHubCrontabHook(IServiceProvider services,
|
||||
IHubContext<SignalRHub> chatHub,
|
||||
ILogger<ChatHubCrontabHook> logger,
|
||||
IUserIdentity user,
|
||||
IConversationStorage storage,
|
||||
BotSharpOptions options,
|
||||
|
|
@ -26,6 +28,7 @@ public class ChatHubCrontabHook : ICrontabHook
|
|||
{
|
||||
_services = services;
|
||||
_chatHub = chatHub;
|
||||
_logger = logger;
|
||||
_user = user;
|
||||
_storage = storage;
|
||||
_options = options;
|
||||
|
|
@ -48,13 +51,26 @@ public class ChatHubCrontabHook : ICrontabHook
|
|||
}
|
||||
}, _options.JsonSerializerOptions);
|
||||
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
await SendEvent(item, json);
|
||||
}
|
||||
|
||||
private async Task SendEvent(CrontabItem item, string json)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _chatHub.Clients.Group(item.ConversationId).SendAsync(GENERATE_NOTIFICATION, json);
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
{
|
||||
await _chatHub.Clients.Group(item.ConversationId).SendAsync(GENERATE_NOTIFICATION, json);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _chatHub.Clients.User(item.UserId).SendAsync(GENERATE_NOTIFICATION, json);
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
await _chatHub.Clients.User(item.UserId).SendAsync(GENERATE_NOTIFICATION, json);
|
||||
_logger.LogWarning($"Failed to send event in {nameof(ChatHubCrontabHook)} (conversation id: {item.ConversationId})." +
|
||||
$"\r\n{ex.Message}\r\n{ex.InnerException}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
private readonly ChatHubSettings _settings;
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly IHubContext<SignalRHub> _chatHub;
|
||||
private readonly ILogger<StreamingLogHook> _logger;
|
||||
private readonly IConversationStateService _state;
|
||||
private readonly IUserIdentity _user;
|
||||
private readonly IAgentService _agentService;
|
||||
|
|
@ -30,6 +31,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
ChatHubSettings settings,
|
||||
IServiceProvider serivces,
|
||||
IHubContext<SignalRHub> chatHub,
|
||||
ILogger<StreamingLogHook> logger,
|
||||
IConversationStateService state,
|
||||
IUserIdentity user,
|
||||
IAgentService agentService,
|
||||
|
|
@ -40,6 +42,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
_settings = settings;
|
||||
_services = serivces;
|
||||
_chatHub = chatHub;
|
||||
_logger = logger;
|
||||
_state = state;
|
||||
_user = user;
|
||||
_agentService = agentService;
|
||||
|
|
@ -82,6 +85,33 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
await SendContentLog(conversationId, input);
|
||||
}
|
||||
|
||||
public async Task OnSessionUpdated(Agent agent, string instruction, FunctionDef[] functions)
|
||||
{
|
||||
var conversationId = _state.GetConversationId();
|
||||
if (string.IsNullOrEmpty(conversationId)) return;
|
||||
|
||||
// Agent queue log
|
||||
var log = $"{instruction}";
|
||||
if (functions.Length > 0)
|
||||
{
|
||||
log += $"\r\n\r\n[FUNCTIONS]:\r\n\r\n{string.Join("\r\n\r\n", functions.Select(x => JsonSerializer.Serialize(x, BotSharpOptions.defaultJsonOptions)))}";
|
||||
}
|
||||
_logger.LogInformation(log);
|
||||
|
||||
var message = new RoleDialogModel(AgentRole.Assistant, log)
|
||||
{
|
||||
MessageId = _routingCtx.MessageId
|
||||
};
|
||||
var input = new ContentLogInputModel(conversationId, message)
|
||||
{
|
||||
Name = agent.Name,
|
||||
AgentId = agent.Id,
|
||||
Source = ContentLogSource.Prompt,
|
||||
Log = log
|
||||
};
|
||||
await SendContentLog(conversationId, input);
|
||||
}
|
||||
|
||||
public async Task OnRenderingTemplate(Agent agent, string name, string content)
|
||||
{
|
||||
if (!_convSettings.ShowVerboseLog) return;
|
||||
|
|
@ -439,54 +469,85 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR
|
|||
#region Private methods
|
||||
private async Task SendContentLog(string conversationId, ContentLogInputModel input)
|
||||
{
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
try
|
||||
{
|
||||
await _chatHub.Clients.Group(conversationId).SendAsync(CONTENT_LOG_GENERATED, BuildContentLog(input));
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
{
|
||||
await _chatHub.Clients.Group(conversationId).SendAsync(CONTENT_LOG_GENERATED, BuildContentLog(input));
|
||||
}
|
||||
else
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(CONTENT_LOG_GENERATED, BuildContentLog(input));
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(CONTENT_LOG_GENERATED, BuildContentLog(input));
|
||||
_logger.LogWarning($"Failed to send content log in {nameof(StreamingLogHook)} (conversation id: {conversationId})." +
|
||||
$"\r\n{ex.Message}\r\n{ex.InnerException}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendStateLog(string conversationId, string agentId, Dictionary<string, string> states, RoleDialogModel message)
|
||||
{
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
try
|
||||
{
|
||||
await _chatHub.Clients.Group(conversationId).SendAsync(STATE_LOG_GENERATED, BuildStateLog(conversationId, agentId, states, message));
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
{
|
||||
await _chatHub.Clients.Group(conversationId).SendAsync(STATE_LOG_GENERATED, BuildStateLog(conversationId, agentId, states, message));
|
||||
}
|
||||
else
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(STATE_LOG_GENERATED, BuildStateLog(conversationId, agentId, states, message));
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(STATE_LOG_GENERATED, BuildStateLog(conversationId, agentId, states, message));
|
||||
_logger.LogWarning($"Failed to send state log in {nameof(StreamingLogHook)} (conversation id: {conversationId})." +
|
||||
$"\r\n{ex.Message}\r\n{ex.InnerException}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendAgentQueueLog(string conversationId, string log)
|
||||
{
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
try
|
||||
{
|
||||
await _chatHub.Clients.Group(conversationId).SendAsync(AGENT_QUEUE_CHANGED, BuildAgentQueueChangedLog(conversationId, log));
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
{
|
||||
await _chatHub.Clients.Group(conversationId).SendAsync(AGENT_QUEUE_CHANGED, BuildAgentQueueChangedLog(conversationId, log));
|
||||
}
|
||||
else
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(AGENT_QUEUE_CHANGED, BuildAgentQueueChangedLog(conversationId, log));
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(AGENT_QUEUE_CHANGED, BuildAgentQueueChangedLog(conversationId, log));
|
||||
_logger.LogWarning($"Failed to send agent queue log in {nameof(StreamingLogHook)} (conversation id: {conversationId})." +
|
||||
$"\r\n{ex.Message}\r\n{ex.InnerException}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendStateChange(string conversationId, StateChangeModel stateChange)
|
||||
{
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
try
|
||||
{
|
||||
await _chatHub.Clients.Group(conversationId).SendAsync(STATE_CHANGED, BuildStateChangeLog(stateChange));
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
{
|
||||
await _chatHub.Clients.Group(conversationId).SendAsync(STATE_CHANGED, BuildStateChangeLog(stateChange));
|
||||
}
|
||||
else
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(STATE_CHANGED, BuildStateChangeLog(stateChange));
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(STATE_CHANGED, BuildStateChangeLog(stateChange));
|
||||
_logger.LogWarning($"Failed to send state change in {nameof(StreamingLogHook)} (conversation id: {conversationId})." +
|
||||
$"\r\n{ex.Message}\r\n{ex.InnerException}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private string BuildContentLog(ContentLogInputModel input)
|
||||
{
|
||||
var output = new ContentLogOutputModel
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ public class WelcomeHook : ConversationHookBase
|
|||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly IHubContext<SignalRHub> _chatHub;
|
||||
private readonly ILogger<WelcomeHook> _logger;
|
||||
private readonly IUserIdentity _user;
|
||||
private readonly IConversationStorage _storage;
|
||||
private readonly BotSharpOptions _options;
|
||||
|
|
@ -17,6 +18,7 @@ public class WelcomeHook : ConversationHookBase
|
|||
|
||||
public WelcomeHook(IServiceProvider services,
|
||||
IHubContext<SignalRHub> chatHub,
|
||||
ILogger<WelcomeHook> logger,
|
||||
IUserIdentity user,
|
||||
IConversationStorage storage,
|
||||
BotSharpOptions options,
|
||||
|
|
@ -24,6 +26,7 @@ public class WelcomeHook : ConversationHookBase
|
|||
{
|
||||
_services = services;
|
||||
_chatHub = chatHub;
|
||||
_logger = logger;
|
||||
_user = user;
|
||||
_storage = storage;
|
||||
_options = options;
|
||||
|
|
@ -78,17 +81,30 @@ public class WelcomeHook : ConversationHookBase
|
|||
|
||||
_storage.Append(conversation.Id, dialog);
|
||||
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
{
|
||||
await _chatHub.Clients.Group(conversation.Id).SendAsync(RECEIVE_ASSISTANT_MESSAGE, json);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(RECEIVE_ASSISTANT_MESSAGE, json);
|
||||
}
|
||||
await SendEvent(conversation.Id, json);
|
||||
}
|
||||
}
|
||||
|
||||
await base.OnUserAgentConnectedInitially(conversation);
|
||||
}
|
||||
|
||||
private async Task SendEvent(string conversationId, string json)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_settings.EventDispatchBy == EventDispatchType.Group)
|
||||
{
|
||||
await _chatHub.Clients.Group(conversationId).SendAsync(RECEIVE_ASSISTANT_MESSAGE, json);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync(RECEIVE_ASSISTANT_MESSAGE, json);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning($"Failed to send event in {nameof(WelcomeHook)} (conversation id: {conversationId})." +
|
||||
$"\r\n{ex.Message}\r\n{ex.InnerException}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,12 +33,7 @@ public class SignalRHub : Hub
|
|||
if (!string.IsNullOrEmpty(conversationId))
|
||||
{
|
||||
_logger.LogInformation($"Connection {Context.ConnectionId} is with conversation {conversationId}");
|
||||
|
||||
var settings = _services.GetRequiredService<ChatHubSettings>();
|
||||
if (settings.EventDispatchBy == EventDispatchType.Group)
|
||||
{
|
||||
await Groups.AddToGroupAsync(Context.ConnectionId, conversationId);
|
||||
}
|
||||
await AddGroup(conversationId);
|
||||
|
||||
var conv = await convService.GetConversation(conversationId);
|
||||
if (conv != null)
|
||||
|
|
@ -56,4 +51,21 @@ public class SignalRHub : Hub
|
|||
|
||||
await base.OnConnectedAsync();
|
||||
}
|
||||
|
||||
private async Task AddGroup(string conversationId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = _services.GetRequiredService<ChatHubSettings>();
|
||||
if (settings.EventDispatchBy == EventDispatchType.Group)
|
||||
{
|
||||
await Groups.AddToGroupAsync(Context.ConnectionId, conversationId);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning($"Failed to add chat group in {nameof(SignalRHub)} (conversation id: {conversationId})." +
|
||||
$"\r\n{ex.Message}\r\n{ex.InnerException}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,10 +8,11 @@
|
|||
<GeneratePackageOnBuild>$(GeneratePackageOnBuild)</GeneratePackageOnBuild>
|
||||
<GenerateDocumentationFile>$(GenerateDocumentationFile)</GenerateDocumentationFile>
|
||||
<OutputPath>$(SolutionDir)packages</OutputPath>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OpenAI" Version="2.1.0" />
|
||||
<PackageReference Include="OpenAI" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
<GeneratePackageOnBuild>$(GeneratePackageOnBuild)</GeneratePackageOnBuild>
|
||||
<GenerateDocumentationFile>$(GenerateDocumentationFile)</GenerateDocumentationFile>
|
||||
<OutputPath>$(SolutionDir)packages</OutputPath>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -33,7 +34,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MailKit" Version="4.7.0" />
|
||||
<PackageReference Include="MailKit" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -27,9 +28,9 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.8" />
|
||||
<PackageReference Include="MySql.Data" Version="9.0.0" />
|
||||
<PackageReference Include="NPOI" Version="2.7.1" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" />
|
||||
<PackageReference Include="MySql.Data" />
|
||||
<PackageReference Include="NPOI" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -8,11 +8,12 @@
|
|||
<GeneratePackageOnBuild>$(GeneratePackageOnBuild)</GeneratePackageOnBuild>
|
||||
<GenerateDocumentationFile>$(GenerateDocumentationFile)</GenerateDocumentationFile>
|
||||
<OutputPath>$(SolutionDir)packages</OutputPath>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="LLMSharp.Google.Palm" Version="1.0.2" />
|
||||
<PackageReference Include="Mscc.GenerativeAI" Version="2.0.1" />
|
||||
<PackageReference Include="LLMSharp.Google.Palm" />
|
||||
<PackageReference Include="Mscc.GenerativeAI" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -8,11 +8,12 @@
|
|||
<GeneratePackageOnBuild>$(GeneratePackageOnBuild)</GeneratePackageOnBuild>
|
||||
<GenerateDocumentationFile>$(GenerateDocumentationFile)</GenerateDocumentationFile>
|
||||
<OutputPath>$(SolutionDir)packages</OutputPath>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
|
||||
<PackageReference Include="Refit.HttpClientFactory" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" />
|
||||
<PackageReference Include="Refit.HttpClientFactory" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -4,10 +4,11 @@
|
|||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Jint" Version="4.1.0" />
|
||||
<PackageReference Include="Jint" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
<GeneratePackageOnBuild>$(GeneratePackageOnBuild)</GeneratePackageOnBuild>
|
||||
<GenerateDocumentationFile>$(GenerateDocumentationFile)</GenerateDocumentationFile>
|
||||
<OutputPath>$(SolutionDir)packages</OutputPath>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -23,8 +24,6 @@
|
|||
<None Remove="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\instructions\instruction.liquid" />
|
||||
<None Remove="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\templates\knowledge.generation.plain.liquid" />
|
||||
<None Remove="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\templates\knowledge.generation.refine.liquid" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-knowledge-knowledge_retrieval.json" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-knowledge-knowledge_retrieval.fn.liquid" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -46,17 +45,17 @@
|
|||
<Content Include="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\templates\knowledge.generation.plain.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-knowledge-knowledge_retrieval.json">
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-kg-knowledge_retrieval.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-knowledge-knowledge_retrieval.fn.liquid">
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-kg-knowledge_retrieval.fn.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="PdfPig" Version="0.1.8" />
|
||||
<PackageReference Include="TensorFlow.Keras" Version="0.15.0" />
|
||||
<PackageReference Include="PdfPig" />
|
||||
<PackageReference Include="TensorFlow.Keras" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -2,5 +2,5 @@ namespace BotSharp.Plugin.KnowledgeBase.Enum;
|
|||
|
||||
public class UtilityName
|
||||
{
|
||||
public const string KnowledgeRetrieval = "knowledge.knowledge-retrieval";
|
||||
public const string KnowledgeRetrieval = "kg.knowledge-base";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ namespace BotSharp.Plugin.KnowledgeBase.Functions;
|
|||
|
||||
public class KnowledgeRetrievalFn : IFunctionCallback
|
||||
{
|
||||
public string Name => "util-knowledge-knowledge_retrieval";
|
||||
public string Name => "util-kg-knowledge_retrieval";
|
||||
|
||||
public string Indication => "searching my brain";
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ namespace BotSharp.Plugin.KnowledgeBase.Hooks;
|
|||
|
||||
public class KnowledgeBaseUtilityHook : IAgentUtilityHook
|
||||
{
|
||||
private static string PREFIX = "util-knowledge-";
|
||||
private static string PREFIX = "util-kg-";
|
||||
private static string KNOWLEDGE_RETRIEVAL_FN = $"{PREFIX}knowledge_retrieval";
|
||||
|
||||
public void AddUtilities(List<AgentUtility> utilities)
|
||||
|
|
|
|||
|
|
@ -40,6 +40,20 @@ public class KnowledgeHook : IKnowledgeHook
|
|||
var result = await _knowledgeService.SearchGraphKnowledge(text, options);
|
||||
results.Add(result.Result);
|
||||
}
|
||||
else if (knowledgeBase.Type == "document")
|
||||
{
|
||||
var options = new VectorSearchOptions
|
||||
{
|
||||
Fields = null,
|
||||
Limit = 5,
|
||||
Confidence = 0.25f,
|
||||
WithVector = true
|
||||
};
|
||||
var result = await _knowledgeService.SearchVectorKnowledge(text, knowledgeBase.Name, options);
|
||||
results.AddRange(result.Where(x => x.Data != null && x.Data.ContainsKey("text"))
|
||||
.Select(x => x.Data["text"].ToString())
|
||||
.Where(x => x != null)!);
|
||||
}
|
||||
else
|
||||
{
|
||||
var options = new VectorSearchOptions
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "util-knowledge-knowledge_retrieval",
|
||||
"name": "util-kg-knowledge_retrieval",
|
||||
"description": "Retrieve related domain knowledge to handle user request",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
Call function util-knowledge-knowledge_retrieval to retrieve related domain knowledge to handle user request.
|
||||
Call function util-kg-knowledge_retrieval to retrieve related domain knowledge to handle user request.
|
||||
You must retrieve existing KnowledgeBase to get prerequisite informations before you writing SQL query;
|
||||
You must retrieve existing API specification from KnowledgeBase before calling a Web API;
|
||||
|
|
@ -8,10 +8,11 @@
|
|||
<GeneratePackageOnBuild>$(GeneratePackageOnBuild)</GeneratePackageOnBuild>
|
||||
<GenerateDocumentationFile>$(GenerateDocumentationFile)</GenerateDocumentationFile>
|
||||
<OutputPath>$(SolutionDir)packages</OutputPath>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="LLamaSharp" Version="0.20.0" />
|
||||
<PackageReference Include="LLamaSharp" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -8,10 +8,11 @@
|
|||
<GeneratePackageOnBuild>$(GeneratePackageOnBuild)</GeneratePackageOnBuild>
|
||||
<GenerateDocumentationFile>$(GenerateDocumentationFile)</GenerateDocumentationFile>
|
||||
<OutputPath>$(SolutionDir)packages</OutputPath>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="LangChain.Providers.Google.VertexAI" Version="0.15.3-dev.58" />
|
||||
<PackageReference Include="LangChain.Providers.Google.VertexAI" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
<GeneratePackageOnBuild>$(GeneratePackageOnBuild)</GeneratePackageOnBuild>
|
||||
<GenerateDocumentationFile>$(GenerateDocumentationFile)</GenerateDocumentationFile>
|
||||
<OutputPath>$(SolutionDir)packages</OutputPath>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -15,8 +16,8 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FaissMask" Version="0.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
|
||||
<PackageReference Include="FastText.NetWrapper" Version="1.3.0" />
|
||||
<PackageReference Include="FaissMask" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" />
|
||||
<PackageReference Include="FastText.NetWrapper" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -8,10 +8,11 @@
|
|||
<GeneratePackageOnBuild>$(GeneratePackageOnBuild)</GeneratePackageOnBuild>
|
||||
<GenerateDocumentationFile>$(GenerateDocumentationFile)</GenerateDocumentationFile>
|
||||
<OutputPath>$(SolutionDir)packages</OutputPath>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.0.0" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Infrastructure\BotSharp.Abstraction\BotSharp.Abstraction.csproj" />
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ public class DialogMongoElement
|
|||
public string? RichContent { get; set; }
|
||||
public string? SecondaryRichContent { get; set; }
|
||||
public string? Payload { get; set; }
|
||||
public object? Data { get; set; }
|
||||
|
||||
public static DialogMongoElement ToMongoElement(DialogElement dialog)
|
||||
{
|
||||
|
|
@ -22,8 +21,7 @@ public class DialogMongoElement
|
|||
SecondaryContent = dialog.SecondaryContent,
|
||||
RichContent = dialog.RichContent,
|
||||
SecondaryRichContent = dialog.SecondaryRichContent,
|
||||
Payload = dialog.Payload,
|
||||
Data = dialog.Data
|
||||
Payload = dialog.Payload
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -36,8 +34,7 @@ public class DialogMongoElement
|
|||
SecondaryContent = dialog.SecondaryContent,
|
||||
RichContent = dialog.RichContent,
|
||||
SecondaryRichContent = dialog.SecondaryRichContent,
|
||||
Payload = dialog.Payload,
|
||||
Data = dialog.Data
|
||||
Payload = dialog.Payload
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -466,16 +466,6 @@ public partial class MongoRepository
|
|||
batchSize = batchLimit;
|
||||
}
|
||||
|
||||
if (bufferHours <= 0)
|
||||
{
|
||||
bufferHours = 12;
|
||||
}
|
||||
|
||||
if (messageLimit <= 0)
|
||||
{
|
||||
messageLimit = 2;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
var skip = (page - 1) * batchSize;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Infrastructure\BotSharp.Abstraction\BotSharp.Abstraction.csproj" />
|
||||
<ProjectReference Include="..\..\Infrastructure\BotSharp.Core\BotSharp.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
using BotSharp.Abstraction.Conversations.Enums;
|
||||
using BotSharp.Abstraction.Files.Utilities;
|
||||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Options;
|
||||
using BotSharp.Abstraction.Realtime.Models;
|
||||
using BotSharp.Core.Infrastructures;
|
||||
using BotSharp.Plugin.OpenAI.Models.Realtime;
|
||||
using OpenAI.Chat;
|
||||
using System.Net.WebSockets;
|
||||
|
|
@ -99,6 +102,23 @@ public class RealTimeCompletionProvider : IRealTimeCompletion
|
|||
});
|
||||
}
|
||||
|
||||
public async Task CancelModelResponse()
|
||||
{
|
||||
await SendEventToModel(new
|
||||
{
|
||||
type = "response.cancel"
|
||||
});
|
||||
}
|
||||
|
||||
public async Task RemoveConversationItem(string itemId)
|
||||
{
|
||||
await SendEventToModel(new
|
||||
{
|
||||
type = "conversation.item.delete",
|
||||
item_id = itemId
|
||||
});
|
||||
}
|
||||
|
||||
private async Task ReceiveMessage(RealtimeHubConnection conn,
|
||||
Action<string> onModelAudioDeltaReceived,
|
||||
Action onModelAudioResponseDone,
|
||||
|
|
@ -166,7 +186,6 @@ public class RealTimeCompletionProvider : IRealTimeCompletion
|
|||
else if (response.Type == "response.done")
|
||||
{
|
||||
_logger.LogInformation($"{response.Type}: {receivedText}");
|
||||
await Task.Delay(1000);
|
||||
var messages = await OnResponsedDone(conn, receivedText);
|
||||
onModelResponseDone(messages);
|
||||
}
|
||||
|
|
@ -204,12 +223,18 @@ public class RealTimeCompletionProvider : IRealTimeCompletion
|
|||
|
||||
public async Task SendEventToModel(object message)
|
||||
{
|
||||
if (_webSocket.State != WebSocketState.Open)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (message is not string data)
|
||||
{
|
||||
data = JsonSerializer.Serialize(message);
|
||||
data = JsonSerializer.Serialize(message, BotSharpOptions.defaultJsonOptions);
|
||||
}
|
||||
|
||||
var buffer = Encoding.UTF8.GetBytes(data);
|
||||
|
||||
await _webSocket.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, CancellationToken.None);
|
||||
}
|
||||
|
||||
|
|
@ -247,19 +272,29 @@ public class RealTimeCompletionProvider : IRealTimeCompletion
|
|||
return session;
|
||||
}
|
||||
|
||||
public async Task UpdateInitialSession(RealtimeHubConnection conn)
|
||||
public async Task UpdateSession(RealtimeHubConnection conn)
|
||||
{
|
||||
var convService = _services.GetRequiredService<IConversationService>();
|
||||
var conv = await convService.GetConversation(conn.ConversationId);
|
||||
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agent = await agentService.LoadAgent(conv.AgentId);
|
||||
var agent = await agentService.LoadAgent(conn.CurrentAgentId);
|
||||
|
||||
var client = ProviderHelper.GetClient(Provider, _model, _services);
|
||||
var chatClient = client.GetChatClient(_model);
|
||||
var (prompt, messages, options) = PrepareOptions(agent, []);
|
||||
|
||||
var instruction = messages.FirstOrDefault()?.Content.FirstOrDefault()?.Text ?? agent.Description;
|
||||
var functions = options.Tools.Select(x =>
|
||||
{
|
||||
var fn = new FunctionDef
|
||||
{
|
||||
Name = x.FunctionName,
|
||||
Description = x.FunctionDescription
|
||||
};
|
||||
fn.Parameters = JsonSerializer.Deserialize<FunctionParametersDef>(x.FunctionParameters);
|
||||
return fn;
|
||||
}).ToArray();
|
||||
|
||||
var sessionUpdate = new
|
||||
{
|
||||
|
|
@ -275,21 +310,23 @@ public class RealTimeCompletionProvider : IRealTimeCompletion
|
|||
Voice = "alloy",
|
||||
Instructions = instruction,
|
||||
ToolChoice = "auto",
|
||||
Tools = options.Tools.Select(x =>
|
||||
{
|
||||
var fn = new FunctionDef
|
||||
{
|
||||
Name = x.FunctionName,
|
||||
Description = x.FunctionDescription
|
||||
};
|
||||
fn.Parameters = JsonSerializer.Deserialize<FunctionParametersDef>(x.FunctionParameters);
|
||||
return fn;
|
||||
}).ToArray(),
|
||||
Tools = functions,
|
||||
Modalities = [ "text", "audio" ],
|
||||
Temperature = Math.Max(options.Temperature ?? 0f, 0.6f)
|
||||
Temperature = Math.Max(options.Temperature ?? 0f, 0.6f),
|
||||
MaxResponseOutputTokens = 512,
|
||||
TurnDetection = new RealtimeSessionTurnDetection
|
||||
{
|
||||
Threshold = 0.8f,
|
||||
SilenceDuration = 800
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await HookEmitter.Emit<IContentGeneratingHook>(_services, async hook =>
|
||||
{
|
||||
await hook.OnSessionUpdated(agent, instruction, functions);
|
||||
});
|
||||
|
||||
await SendEventToModel(sessionUpdate);
|
||||
}
|
||||
|
||||
|
|
@ -550,16 +587,23 @@ public class RealTimeCompletionProvider : IRealTimeCompletion
|
|||
var outputs = new List<RoleDialogModel>();
|
||||
|
||||
var data = JsonSerializer.Deserialize<ResponseDone>(response).Body;
|
||||
if (data.Status != "completed")
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach (var output in data.Outputs)
|
||||
{
|
||||
if (output.Type == "function_call")
|
||||
{
|
||||
outputs.Add(new RoleDialogModel(output.Role, output.Arguments)
|
||||
{
|
||||
CurrentAgentId = conn.EntryAgentId,
|
||||
CurrentAgentId = conn.CurrentAgentId,
|
||||
FunctionName = output.Name,
|
||||
FunctionArgs = output.Arguments,
|
||||
ToolCallId = output.CallId
|
||||
ToolCallId = output.CallId,
|
||||
MessageId = output.Id,
|
||||
MessageType = MessageTypeName.FunctionCall
|
||||
});
|
||||
}
|
||||
else if (output.Type == "message")
|
||||
|
|
@ -568,11 +612,27 @@ public class RealTimeCompletionProvider : IRealTimeCompletion
|
|||
|
||||
outputs.Add(new RoleDialogModel(output.Role, content.Transcript)
|
||||
{
|
||||
CurrentAgentId = conn.EntryAgentId
|
||||
CurrentAgentId = conn.CurrentAgentId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var contentHooks = _services.GetServices<IContentGeneratingHook>().ToList();
|
||||
// After chat completion hook
|
||||
foreach (var hook in contentHooks)
|
||||
{
|
||||
await hook.AfterGenerated(new RoleDialogModel(AgentRole.Assistant, "response.done")
|
||||
{
|
||||
CurrentAgentId = conn.CurrentAgentId
|
||||
}, new TokenStatsModel
|
||||
{
|
||||
Provider = Provider,
|
||||
Model = _model,
|
||||
CompletionCount = data.Usage.OutputTokens,
|
||||
PromptCount = data.Usage.InputTokens
|
||||
});
|
||||
}
|
||||
|
||||
return outputs;
|
||||
}
|
||||
|
||||
|
|
@ -581,7 +641,7 @@ public class RealTimeCompletionProvider : IRealTimeCompletion
|
|||
var data = JsonSerializer.Deserialize<ResponseAudioTranscript>(response);
|
||||
return new RoleDialogModel(AgentRole.User, data.Transcript)
|
||||
{
|
||||
CurrentAgentId = conn.EntryAgentId
|
||||
CurrentAgentId = conn.CurrentAgentId
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using System.Net.Http;
|
|||
using System.Net.Mime;
|
||||
using System.Text.Json;
|
||||
using System.Text;
|
||||
using BotSharp.Abstraction.Options;
|
||||
|
||||
namespace BotSharp.Plugin.OpenAI.Providers.Text;
|
||||
|
||||
|
|
@ -13,14 +14,6 @@ public class TextCompletionProvider : ITextCompletion
|
|||
private readonly OpenAiSettings _settings;
|
||||
protected string _model;
|
||||
|
||||
protected readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
WriteIndented = true,
|
||||
AllowTrailingCommas = true,
|
||||
};
|
||||
|
||||
public virtual string Provider => "openai";
|
||||
|
||||
public TextCompletionProvider(
|
||||
|
|
@ -110,7 +103,7 @@ public class TextCompletionProvider : ITextCompletion
|
|||
MaxTokens = maxTokens,
|
||||
Temperature = temperature
|
||||
};
|
||||
var data = JsonSerializer.Serialize(request, _jsonOptions);
|
||||
var data = JsonSerializer.Serialize(request, BotSharpOptions.defaultJsonOptions);
|
||||
var httpRequest = new HttpRequestMessage
|
||||
{
|
||||
Method = HttpMethod.Post,
|
||||
|
|
@ -121,7 +114,7 @@ public class TextCompletionProvider : ITextCompletion
|
|||
var httpResponse = await httpClient.SendAsync(httpRequest);
|
||||
httpResponse.EnsureSuccessStatusCode();
|
||||
var responseStr = await httpResponse.Content.ReadAsStringAsync();
|
||||
var response = JsonSerializer.Deserialize<TextCompletionResponse>(responseStr, _jsonOptions);
|
||||
var response = JsonSerializer.Deserialize<TextCompletionResponse>(responseStr, BotSharpOptions.defaultJsonOptions);
|
||||
return response;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
|
|||
|
|
@ -9,15 +9,16 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-twilio-twilio_outbound_phone_call.json" />
|
||||
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-twilio-twilio_outbound_phone_call.fn.liquid" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-twilio-twilio_outbound_phone_call.json">
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-twilio-hangup_phone_call.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-twilio-twilio_outbound_phone_call.fn.liquid">
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-twilio-outbound_phone_call.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-twilio-hangup_phone_call.fn.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-twilio-outbound_phone_call.fn.liquid">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
using BotSharp.Abstraction.Infrastructures;
|
||||
using BotSharp.Abstraction.Infrastructures.Enums;
|
||||
using BotSharp.Core.Infrastructures;
|
||||
using BotSharp.Plugin.Twilio.Interfaces;
|
||||
using BotSharp.Plugin.Twilio.Models;
|
||||
using BotSharp.Plugin.Twilio.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Twilio.TwiML.Voice;
|
||||
using Conversation = BotSharp.Abstraction.Conversations.Models.Conversation;
|
||||
using Task = System.Threading.Tasks.Task;
|
||||
|
||||
namespace BotSharp.Plugin.Twilio.Controllers;
|
||||
|
||||
|
|
@ -52,10 +51,6 @@ public class TwilioStreamController : TwilioController
|
|||
{
|
||||
request.ConversationId = _context.HttpContext.Request.Query["conversation_id"];
|
||||
}
|
||||
else
|
||||
{
|
||||
request.ConversationId = request.CallSid;
|
||||
}
|
||||
|
||||
await HookEmitter.Emit<ITwilioSessionHook>(_services, async hook =>
|
||||
{
|
||||
|
|
@ -65,7 +60,7 @@ public class TwilioStreamController : TwilioController
|
|||
OnlyOnce = true
|
||||
});
|
||||
|
||||
await InitConversation(request);
|
||||
request.ConversationId = await InitConversation(request);
|
||||
|
||||
var twilio = _services.GetRequiredService<TwilioService>();
|
||||
|
||||
|
|
@ -82,7 +77,7 @@ public class TwilioStreamController : TwilioController
|
|||
return TwiML(response);
|
||||
}
|
||||
|
||||
private async Task InitConversation(ConversationalVoiceRequest request)
|
||||
private async Task<string> InitConversation(ConversationalVoiceRequest request)
|
||||
{
|
||||
var convService = _services.GetRequiredService<IConversationService>();
|
||||
var conversation = await convService.GetConversation(request.ConversationId);
|
||||
|
|
@ -90,10 +85,10 @@ public class TwilioStreamController : TwilioController
|
|||
{
|
||||
var conv = new Conversation
|
||||
{
|
||||
Id = request.CallSid,
|
||||
AgentId = _settings.AgentId,
|
||||
AgentId = request.AgentId ?? _settings.AgentId,
|
||||
Channel = ConversationChannel.Phone,
|
||||
Title = $"Phone call from {request.From}",
|
||||
ChannelId = request.CallSid,
|
||||
Title = $"Incoming phone call from {request.From}",
|
||||
Tags = [],
|
||||
};
|
||||
|
||||
|
|
@ -103,10 +98,15 @@ public class TwilioStreamController : TwilioController
|
|||
var states = new List<MessageState>
|
||||
{
|
||||
new("channel", ConversationChannel.Phone),
|
||||
new("calling_phone", request.From)
|
||||
new("calling_phone", request.From),
|
||||
new("twilio_call_sid", request.CallSid),
|
||||
// Enable lazy routing mode to optimize realtime experience
|
||||
new(StateConst.ROUTING_MODE, "lazy"),
|
||||
};
|
||||
|
||||
convService.SetConversationId(conversation.Id, states);
|
||||
convService.SaveStates();
|
||||
|
||||
return conversation.Id;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ public class TwilioVoiceController : TwilioController
|
|||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
// [ValidateRequest]
|
||||
[ValidateRequest]
|
||||
[HttpPost("twilio/voice/{conversationId}/receive/{seqNum}")]
|
||||
public async Task<TwiMLResult> ReceiveCallerMessage(ConversationalVoiceRequest request)
|
||||
{
|
||||
|
|
@ -202,7 +202,7 @@ public class TwilioVoiceController : TwilioController
|
|||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
// [ValidateRequest]
|
||||
[ValidateRequest]
|
||||
[HttpPost("twilio/voice/{conversationId}/reply/{seqNum}")]
|
||||
public async Task<TwiMLResult> ReplyCallerMessage(ConversationalVoiceRequest request)
|
||||
{
|
||||
|
|
@ -367,7 +367,7 @@ public class TwilioVoiceController : TwilioController
|
|||
return TwiML(response);
|
||||
}
|
||||
|
||||
// [ValidateRequest]
|
||||
[ValidateRequest]
|
||||
[HttpPost("twilio/voice/init-call")]
|
||||
public TwiMLResult InitiateOutboundCall(VoiceRequest request, [Required][FromQuery] string conversationId)
|
||||
{
|
||||
|
|
@ -388,7 +388,7 @@ public class TwilioVoiceController : TwilioController
|
|||
return TwiML(response);
|
||||
}
|
||||
|
||||
// [ValidateRequest]
|
||||
[ValidateRequest]
|
||||
[HttpGet("twilio/voice/speeches/{conversationId}/{fileName}")]
|
||||
public async Task<FileContentResult> GetSpeechFile([FromRoute] string conversationId, [FromRoute] string fileName)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ namespace BotSharp.Plugin.Twilio.Models;
|
|||
|
||||
public class ConversationalVoiceRequest : VoiceRequest
|
||||
{
|
||||
[FromQuery(Name = "agent-id")]
|
||||
public string AgentId { get; set; }
|
||||
|
||||
[FromRoute]
|
||||
public string ConversationId { get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@ namespace BotSharp.Plugin.Twilio.OutboundPhoneCallHandler.Enums
|
|||
{
|
||||
public class UtilityName
|
||||
{
|
||||
public const string OutboundPhoneCall = "twilio.twilio-outbound-phone-call";
|
||||
public const string OutboundPhoneCall = "phone.twilio-phone-call";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,106 +0,0 @@
|
|||
using BotSharp.Abstraction.Files;
|
||||
using BotSharp.Abstraction.Infrastructures.Enums;
|
||||
using BotSharp.Abstraction.Options;
|
||||
using BotSharp.Abstraction.Routing;
|
||||
using BotSharp.Core.Infrastructures;
|
||||
using BotSharp.Plugin.Twilio.Interfaces;
|
||||
using BotSharp.Plugin.Twilio.Models;
|
||||
using BotSharp.Plugin.Twilio.OutboundPhoneCallHandler.LlmContexts;
|
||||
using Twilio.Rest.Api.V2010.Account;
|
||||
using Twilio.Types;
|
||||
|
||||
namespace BotSharp.Plugin.Twilio.OutboundPhoneCallHandler.Functions
|
||||
{
|
||||
public class HandleOutboundPhoneCallFn : IFunctionCallback
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger<HandleOutboundPhoneCallFn> _logger;
|
||||
private readonly BotSharpOptions _options;
|
||||
private readonly TwilioSetting _twilioSetting;
|
||||
|
||||
public string Name => "util-twilio-twilio_outbound_phone_call";
|
||||
public string Indication => "Dialing the number";
|
||||
|
||||
public HandleOutboundPhoneCallFn(
|
||||
IServiceProvider services,
|
||||
ILogger<HandleOutboundPhoneCallFn> logger,
|
||||
BotSharpOptions options,
|
||||
TwilioSetting twilioSetting)
|
||||
{
|
||||
_services = services;
|
||||
_logger = logger;
|
||||
_options = options;
|
||||
_twilioSetting = twilioSetting;
|
||||
}
|
||||
|
||||
public async Task<bool> Execute(RoleDialogModel message)
|
||||
{
|
||||
var args = JsonSerializer.Deserialize<LlmContextIn>(message.FunctionArgs, _options.JsonSerializerOptions);
|
||||
if (args.PhoneNumber.Length != 12 || !args.PhoneNumber.StartsWith("+1", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var error = $"Invalid phone number format: {args.PhoneNumber}";
|
||||
_logger.LogError(error);
|
||||
message.Content = error;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(args.InitialMessage))
|
||||
{
|
||||
_logger.LogError("Initial message is empty.");
|
||||
message.Content = "There is an error when generating phone message.";
|
||||
return false;
|
||||
}
|
||||
|
||||
var convService = _services.GetRequiredService<IConversationService>();
|
||||
var convStorage = _services.GetRequiredService<IConversationStorage>();
|
||||
var routing = _services.GetRequiredService<IRoutingContext>();
|
||||
var fileStorage = _services.GetRequiredService<IFileStorageService>();
|
||||
var sessionManager = _services.GetRequiredService<ITwilioSessionManager>();
|
||||
var states = _services.GetRequiredService<IConversationStateService>();
|
||||
|
||||
// Fork conversation
|
||||
var entryAgentId = routing.EntryAgentId;
|
||||
var newConv = await convService.NewConversation(new Abstraction.Conversations.Models.Conversation
|
||||
{
|
||||
AgentId = entryAgentId,
|
||||
Channel = ConversationChannel.Phone
|
||||
});
|
||||
var conversationId = newConv.Id;
|
||||
convStorage.Append(conversationId, new List<RoleDialogModel>
|
||||
{
|
||||
new RoleDialogModel(AgentRole.User, "Hi")
|
||||
{
|
||||
CurrentAgentId = entryAgentId
|
||||
},
|
||||
new RoleDialogModel(AgentRole.Assistant, args.InitialMessage)
|
||||
{
|
||||
CurrentAgentId = entryAgentId
|
||||
}
|
||||
});
|
||||
states.SetState(StateConst.SUB_CONVERSATION_ID, conversationId);
|
||||
|
||||
// Generate audio
|
||||
var completion = CompletionProvider.GetAudioCompletion(_services, "openai", "tts-1");
|
||||
var data = await completion.GenerateAudioFromTextAsync(args.InitialMessage);
|
||||
var fileName = $"intial.mp3";
|
||||
fileStorage.SaveSpeechFile(conversationId, fileName, data);
|
||||
|
||||
// Call phone number
|
||||
/*await sessionManager.SetAssistantReplyAsync(conversationId, 0, new AssistantMessage
|
||||
{
|
||||
Content = args.InitialMessage,
|
||||
SpeechFileName = fileName
|
||||
});*/
|
||||
|
||||
var call = await CallResource.CreateAsync(
|
||||
// url: new Uri($"{_twilioSetting.CallbackHost}/twilio/voice/init-call?conversationId={conversationId}"),
|
||||
url: new Uri($"{_twilioSetting.CallbackHost}/twilio/stream?conversation_id={conversationId}&init_audio_file={fileName}"),
|
||||
to: new PhoneNumber(args.PhoneNumber),
|
||||
from: new PhoneNumber(_twilioSetting.PhoneNumber));
|
||||
|
||||
message.Content = $"The generated phone message: {args.InitialMessage}." ?? message.Content;
|
||||
message.StopCompletion = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
using Twilio.Rest.Api.V2010.Account;
|
||||
|
||||
namespace BotSharp.Plugin.Twilio.OutboundPhoneCallHandler.Functions;
|
||||
|
||||
public class HangupPhoneCallFn : IFunctionCallback
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger<HangupPhoneCallFn> _logger;
|
||||
|
||||
public string Name => "util-twilio-hangup_phone_call";
|
||||
public string Indication => "Hangup";
|
||||
|
||||
public HangupPhoneCallFn(
|
||||
IServiceProvider services,
|
||||
ILogger<HangupPhoneCallFn> logger)
|
||||
{
|
||||
_services = services;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<bool> Execute(RoleDialogModel message)
|
||||
{
|
||||
var states = _services.GetRequiredService<IConversationStateService>();
|
||||
var callSid = states.GetState("twilio_call_sid");
|
||||
|
||||
if (string.IsNullOrEmpty(callSid))
|
||||
{
|
||||
message.Content = "The call has not been initiated.";
|
||||
_logger.LogError(message.Content);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Have to find the SID by the phone number
|
||||
var call = CallResource.Update(
|
||||
status: CallResource.UpdateStatusEnum.Completed,
|
||||
pathSid: callSid
|
||||
);
|
||||
|
||||
message.Content = "The call has ended.";
|
||||
message.StopCompletion = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
using BotSharp.Abstraction.Files;
|
||||
using BotSharp.Abstraction.Infrastructures.Enums;
|
||||
using BotSharp.Abstraction.Options;
|
||||
using BotSharp.Abstraction.Routing;
|
||||
using BotSharp.Core.Infrastructures;
|
||||
using BotSharp.Plugin.Twilio.OutboundPhoneCallHandler.LlmContexts;
|
||||
using Twilio.Rest.Api.V2010.Account;
|
||||
using Twilio.Types;
|
||||
using Conversation = BotSharp.Abstraction.Conversations.Models.Conversation;
|
||||
using Task = System.Threading.Tasks.Task;
|
||||
|
||||
namespace BotSharp.Plugin.Twilio.OutboundPhoneCallHandler.Functions;
|
||||
|
||||
public class OutboundPhoneCallFn : IFunctionCallback
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger<OutboundPhoneCallFn> _logger;
|
||||
private readonly BotSharpOptions _options;
|
||||
private readonly TwilioSetting _twilioSetting;
|
||||
|
||||
public string Name => "util-twilio-outbound_phone_call";
|
||||
public string Indication => "Dialing the phone number";
|
||||
|
||||
public OutboundPhoneCallFn(
|
||||
IServiceProvider services,
|
||||
ILogger<OutboundPhoneCallFn> logger,
|
||||
BotSharpOptions options,
|
||||
TwilioSetting twilioSetting)
|
||||
{
|
||||
_services = services;
|
||||
_logger = logger;
|
||||
_options = options;
|
||||
_twilioSetting = twilioSetting;
|
||||
}
|
||||
|
||||
public async Task<bool> Execute(RoleDialogModel message)
|
||||
{
|
||||
var args = JsonSerializer.Deserialize<LlmContextIn>(message.FunctionArgs, _options.JsonSerializerOptions);
|
||||
if (args.PhoneNumber.Length != 12 || !args.PhoneNumber.StartsWith("+1", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var error = $"Invalid phone number format: {args.PhoneNumber}";
|
||||
_logger.LogError(error);
|
||||
message.Content = error;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(args.InitialMessage))
|
||||
{
|
||||
_logger.LogError("Initial message is empty.");
|
||||
message.Content = "There is an error when generating phone message.";
|
||||
return false;
|
||||
}
|
||||
|
||||
var fileStorage = _services.GetRequiredService<IFileStorageService>();
|
||||
var states = _services.GetRequiredService<IConversationStateService>();
|
||||
|
||||
// Fork conversation
|
||||
var newConversationId = Guid.NewGuid().ToString();
|
||||
states.SetState(StateConst.SUB_CONVERSATION_ID, newConversationId);
|
||||
|
||||
// Generate initial assistant audio
|
||||
var completion = CompletionProvider.GetAudioCompletion(_services, "openai", "tts-1");
|
||||
var data = await completion.GenerateAudioFromTextAsync(args.InitialMessage);
|
||||
var fileName = $"intial.mp3";
|
||||
fileStorage.SaveSpeechFile(newConversationId, fileName, data);
|
||||
|
||||
// Make outbound call
|
||||
var call = await CallResource.CreateAsync(
|
||||
url: new Uri($"{_twilioSetting.CallbackHost}/twilio/stream?conversation_id={newConversationId}&init_audio_file={fileName}"),
|
||||
to: new PhoneNumber(args.PhoneNumber),
|
||||
from: new PhoneNumber(_twilioSetting.PhoneNumber));
|
||||
|
||||
var convService = _services.GetRequiredService<IConversationService>();
|
||||
var routing = _services.GetRequiredService<IRoutingContext>();
|
||||
var originConversationId = convService.ConversationId;
|
||||
var entryAgentId = routing.EntryAgentId;
|
||||
|
||||
await ForkConversation(args, entryAgentId, originConversationId, newConversationId, call);
|
||||
|
||||
message.Content = $"The generated phone message: \"{args.InitialMessage}.\" [NEW CONVERSATION ID: {newConversationId}, TWILIO CALL SID: {call.Sid}]";
|
||||
message.StopCompletion = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task ForkConversation(LlmContextIn args,
|
||||
string entryAgentId,
|
||||
string originConversationId,
|
||||
string newConversationId,
|
||||
CallResource resource)
|
||||
{
|
||||
// new scope service for isolated conversation
|
||||
using var scope = _services.CreateScope();
|
||||
var services = scope.ServiceProvider;
|
||||
var convService = services.GetRequiredService<IConversationService>();
|
||||
var convStorage = services.GetRequiredService<IConversationStorage>();
|
||||
|
||||
var newConv = await convService.NewConversation(new Conversation
|
||||
{
|
||||
Id = newConversationId,
|
||||
AgentId = entryAgentId,
|
||||
Channel = ConversationChannel.Phone,
|
||||
ChannelId = resource.Sid,
|
||||
Title = args.InitialMessage
|
||||
});
|
||||
|
||||
convStorage.Append(newConversationId, new List<RoleDialogModel>
|
||||
{
|
||||
new RoleDialogModel(AgentRole.User, "Hi")
|
||||
{
|
||||
CurrentAgentId = entryAgentId
|
||||
},
|
||||
new RoleDialogModel(AgentRole.Assistant, args.InitialMessage)
|
||||
{
|
||||
CurrentAgentId = entryAgentId
|
||||
}
|
||||
});
|
||||
|
||||
convService.SetConversationId(newConversationId,
|
||||
[
|
||||
new MessageState(StateConst.ORIGIN_CONVERSATION_ID, originConversationId),
|
||||
new MessageState("phone_number", resource.To)
|
||||
]);
|
||||
convService.SaveStates();
|
||||
}
|
||||
}
|
||||
|
|
@ -6,15 +6,24 @@ namespace BotSharp.Plugin.Twilio.OutboundPhoneCallHandler.Hooks;
|
|||
public class OutboundPhoneCallHandlerUtilityHook : IAgentUtilityHook
|
||||
{
|
||||
private static string PREFIX = "util-twilio-";
|
||||
private static string OUTBOUND_PHONE_CALL_FN = $"{PREFIX}twilio_outbound_phone_call";
|
||||
private static string OUTBOUND_PHONE_CALL_FN = $"{PREFIX}outbound_phone_call";
|
||||
private static string HANGUP_PHONE_CALL_FN = $"{PREFIX}hangup_phone_call";
|
||||
|
||||
public void AddUtilities(List<AgentUtility> utilities)
|
||||
{
|
||||
var utility = new AgentUtility
|
||||
{
|
||||
Name = UtilityName.OutboundPhoneCall,
|
||||
Functions = [new($"{OUTBOUND_PHONE_CALL_FN}")],
|
||||
Templates = [new($"{OUTBOUND_PHONE_CALL_FN}.fn")]
|
||||
Functions =
|
||||
[
|
||||
new($"{OUTBOUND_PHONE_CALL_FN}"),
|
||||
new($"{HANGUP_PHONE_CALL_FN}")
|
||||
],
|
||||
Templates =
|
||||
[
|
||||
new($"{OUTBOUND_PHONE_CALL_FN}.fn"),
|
||||
new($"{HANGUP_PHONE_CALL_FN}.fn")
|
||||
]
|
||||
};
|
||||
|
||||
utilities.Add(utility);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"name": "util-twilio-hangup_phone_call",
|
||||
"description": "Call this function if the user wants to end the phone call",
|
||||
"visibility_expression": "{% if states.channel == 'phone' %}visible{% endif %}",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
},
|
||||
"required": []
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,16 @@
|
|||
{
|
||||
"name": "util-twilio-twilio_outbound_phone_call",
|
||||
"name": "util-twilio-outbound_phone_call",
|
||||
"description": "If the user wants to initiate a phone call, you need to capture the phone number and compose the message the users wants to send. Then call this function to make an outbound call via Twilio.",
|
||||
"visibility_expression": "{% if states.channel != 'phone' %}visible{% endif %}",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"phone_number": {
|
||||
"to_read": "string",
|
||||
"type": "string",
|
||||
"description": "The phone number which will be dialed. It needs to be a valid phone number starting with +1."
|
||||
},
|
||||
"initial_message": {
|
||||
"to_read": "string",
|
||||
"type": "string",
|
||||
"description": "The initial message which will be sent."
|
||||
}
|
||||
},
|
||||
|
|
@ -0,0 +1 @@
|
|||
** Please call util-twilio-hangup_phone_call if user wants to end the phone call.
|
||||
|
|
@ -0,0 +1 @@
|
|||
** Please call util-twilio-outbound_phone_call if user wants to make an outbound call.
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
** Please take a look at the conversation and decide whether user wants to make an outbound call.
|
||||
** Please call util-twilio-twilio_outbound_phone_call if user wants to make an outbound call.
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "8970b1e5-d260-4e2c-90b1-f1415a257c18",
|
||||
"name": "Pizza Bot",
|
||||
"description": "AI assistant that can help customer place pizza order.",
|
||||
"description": "AI assistant that can help customer place pizza order, make payment or inquiry order status.",
|
||||
"type": "routing",
|
||||
"inheritAgentId": "01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a",
|
||||
"createdDateTime": "2023-08-18T10:39:32.2349685Z",
|
||||
|
|
@ -10,10 +10,11 @@
|
|||
"disabled": false,
|
||||
"isPublic": true,
|
||||
"profiles": [ "pizza" ],
|
||||
"labels": [ "experiment" ],
|
||||
"routingRules": [
|
||||
{
|
||||
"type": "reasoner",
|
||||
"field": "NaiveReasoner"
|
||||
"field": "Naive Reasoner"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -6,5 +6,6 @@
|
|||
"id": "b284db86-e9c2-4c25-a59e-4649797dd130",
|
||||
"disabled": false,
|
||||
"isPublic": true,
|
||||
"profiles": [ "pizza" ]
|
||||
"profiles": [ "pizza" ],
|
||||
"labels": [ "experiment" ]
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "Ordering",
|
||||
"description": "Provide types of pizza available, unit price and total cost. Place the order and returned the order number.",
|
||||
"description": "Provide types of pizza available, unit price, total cost and place the order.",
|
||||
"createdDateTime": "2023-07-26T02:29:25.123224Z",
|
||||
"updatedDateTime": "2023-07-26T02:29:25.123274Z",
|
||||
"id": "c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd",
|
||||
|
|
@ -21,4 +21,5 @@
|
|||
]
|
||||
}
|
||||
]
|
||||
"labels": [ "experiment" ]
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "Payment",
|
||||
"description": "Make payment when user wants to pay for the order",
|
||||
"description": "Make payment when user confirmed the price and going to pay for the order",
|
||||
"createdDateTime": "2023-07-26T02:29:25.123224Z",
|
||||
"updatedDateTime": "2023-07-26T02:29:25.123274Z",
|
||||
"id": "fe8c60aa-b114-4ef3-93cb-a8efeac80f75",
|
||||
|
|
@ -18,6 +18,7 @@
|
|||
]
|
||||
}
|
||||
],
|
||||
"labels": [ "experiment" ],
|
||||
"routingRules": [
|
||||
{
|
||||
"field": "order_number",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel" Version="1.16.0" />
|
||||
<PackageReference Include="Moq" Version="4.20.70" />
|
||||
|
|
|
|||
Loading…
Reference in a new issue