Merge branch 'SciSharp:master' into master
This commit is contained in:
commit
38ceb0cc38
11
BotSharp.sln
11
BotSharp.sln
|
|
@ -65,6 +65,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.GoogleAI",
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.SemanticKernel", "src\Plugins\BotSharp.Plugin.SemanticKernel\BotSharp.Plugin.SemanticKernel.csproj", "{73EE2CD0-3B27-4F02-A67B-762CBDD740D0}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.MicrosoftExtensionsAI", "src\Plugins\BotSharp.Plugin.MicrosoftExtensionsAI\BotSharp.Plugin.MicrosoftExtensionsAI.csproj", "{72CA059E-6AAA-406C-A1EB-A2243E652F5F}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.SemanticKernel.UnitTests", "tests\BotSharp.Plugin.SemanticKernel.UnitTests\BotSharp.Plugin.SemanticKernel.UnitTests.csproj", "{BC57D428-A1A4-4D38-A2D0-AC6CA943F247}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.Twilio", "src\Plugins\BotSharp.Plugin.Twilio\BotSharp.Plugin.Twilio.csproj", "{E627F1E3-BE03-443A-83A2-86A855A278EB}"
|
||||
|
|
@ -283,6 +285,14 @@ Global
|
|||
{73EE2CD0-3B27-4F02-A67B-762CBDD740D0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{73EE2CD0-3B27-4F02-A67B-762CBDD740D0}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{73EE2CD0-3B27-4F02-A67B-762CBDD740D0}.Release|x64.Build.0 = Release|Any CPU
|
||||
{72CA059E-6AAA-406C-A1EB-A2243E652F5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{72CA059E-6AAA-406C-A1EB-A2243E652F5F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{72CA059E-6AAA-406C-A1EB-A2243E652F5F}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{72CA059E-6AAA-406C-A1EB-A2243E652F5F}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{72CA059E-6AAA-406C-A1EB-A2243E652F5F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{72CA059E-6AAA-406C-A1EB-A2243E652F5F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{72CA059E-6AAA-406C-A1EB-A2243E652F5F}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{72CA059E-6AAA-406C-A1EB-A2243E652F5F}.Release|x64.Build.0 = Release|Any CPU
|
||||
{BC57D428-A1A4-4D38-A2D0-AC6CA943F247}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BC57D428-A1A4-4D38-A2D0-AC6CA943F247}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BC57D428-A1A4-4D38-A2D0-AC6CA943F247}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
|
|
@ -491,6 +501,7 @@ Global
|
|||
{DB3DE37B-1208-4ED3-9615-A52AD0AAD69C} = {5CD330E1-9E5A-4112-8346-6E31CA98EF78}
|
||||
{8BC29F8A-78D6-422C-B522-10687ADC38ED} = {D5293208-2BEF-42FC-A64C-5954F61720BA}
|
||||
{73EE2CD0-3B27-4F02-A67B-762CBDD740D0} = {D5293208-2BEF-42FC-A64C-5954F61720BA}
|
||||
{72CA059E-6AAA-406C-A1EB-A2243E652F5F} = {D5293208-2BEF-42FC-A64C-5954F61720BA}
|
||||
{BC57D428-A1A4-4D38-A2D0-AC6CA943F247} = {32FAFFFE-A4CB-4FEE-BF7C-84518BBC6DCC}
|
||||
{E627F1E3-BE03-443A-83A2-86A855A278EB} = {64264688-0F5C-4AB0-8F2B-B59B717CCE00}
|
||||
{F06B22CB-B143-4680-8FFF-35B9E50E6C47} = {51AFE054-AE99-497D-A593-69BAEFB5106F}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ public interface IKnowledgeHook
|
|||
Task<List<KnowledgeChunk>> CollectChunkedKnowledge()
|
||||
=> Task.FromResult(new List<KnowledgeChunk>());
|
||||
|
||||
Task<List<string>> GetRelevantKnowledges(RoleDialogModel message, string text)
|
||||
Task<List<string>> GetDomainKnowledges(RoleDialogModel message, string text)
|
||||
=> Task.FromResult(new List<string>());
|
||||
|
||||
Task<List<string>> GetGlobalKnowledges(RoleDialogModel message)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ public class KnowledgeRetrievalFn : IFunctionCallback
|
|||
var knowledges = new List<string>();
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
var k = await hook.GetRelevantKnowledges(message, args.Question);
|
||||
var k = await hook.GetDomainKnowledges(message, args.Question);
|
||||
knowledges.AddRange(k);
|
||||
}
|
||||
knowledges = knowledges.Distinct().ToList();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>$(TargetFramework)</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>$(LangVersion)</LangVersion>
|
||||
<VersionPrefix>$(BotSharpVersion)</VersionPrefix>
|
||||
<GeneratePackageOnBuild>$(GeneratePackageOnBuild)</GeneratePackageOnBuild>
|
||||
<GenerateDocumentationFile>$(GeneratePackageOnBuild)</GenerateDocumentationFile>
|
||||
<OutputPath>$(SolutionDir)packages</OutputPath>
|
||||
<NoWarn>$(NoWarn);NU5104</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.AI.Abstractions" Version="9.0.0-preview.9.24525.1" />
|
||||
<PackageReference Include="System.Text.Encodings.Web" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Infrastructure\BotSharp.Abstraction\BotSharp.Abstraction.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
using BotSharp.Abstraction.Agents.Enums;
|
||||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Agents;
|
||||
using BotSharp.Abstraction.Conversations.Models;
|
||||
using BotSharp.Abstraction.Conversations;
|
||||
using BotSharp.Abstraction.Files;
|
||||
using BotSharp.Abstraction.Files.Utilities;
|
||||
using BotSharp.Abstraction.Loggers;
|
||||
using BotSharp.Abstraction.MLTasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
|
||||
namespace BotSharp.Plugin.MicrosoftExtensionsAI;
|
||||
|
||||
/// <summary>
|
||||
/// Provides an implementation of <see cref="IChatCompletion"/> for Microsoft.Extensions.AI.
|
||||
/// </summary>
|
||||
public sealed class MicrosoftExtensionsAIChatCompletionProvider : IChatCompletion
|
||||
{
|
||||
private readonly IChatClient _client;
|
||||
private readonly ILogger<MicrosoftExtensionsAIChatCompletionProvider> _logger;
|
||||
private readonly IServiceProvider _services;
|
||||
private string? _model;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of the <see cref="MicrosoftExtensionsAIChatCompletionProvider"/> class.
|
||||
/// </summary>
|
||||
public MicrosoftExtensionsAIChatCompletionProvider(
|
||||
IChatClient client,
|
||||
ILogger<MicrosoftExtensionsAIChatCompletionProvider> logger,
|
||||
IServiceProvider services)
|
||||
{
|
||||
_client = client;
|
||||
_model = _client.Metadata.ModelId;
|
||||
_logger = logger;
|
||||
_services = services;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Provider => "microsoft.extensions.ai";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetModelName(string model) => _model = model;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<RoleDialogModel> GetChatCompletions(Agent agent, List<RoleDialogModel> conversations)
|
||||
{
|
||||
// Before chat completion hook
|
||||
var hooks = _services.GetServices<IContentGeneratingHook>().ToArray();
|
||||
await Task.WhenAll(hooks.Select(hook => hook.BeforeGenerating(agent, conversations)));
|
||||
|
||||
// Configure options
|
||||
var state = _services.GetRequiredService<IConversationStateService>();
|
||||
var options = new ChatOptions()
|
||||
{
|
||||
Temperature = float.Parse(state.GetState("temperature", "0.0")),
|
||||
MaxOutputTokens = int.Parse(state.GetState("max_tokens", "1024"))
|
||||
};
|
||||
|
||||
if (_services.GetService<IAgentService>() is { } agentService)
|
||||
{
|
||||
foreach (var function in agent.Functions)
|
||||
{
|
||||
if (agentService.RenderFunction(agent, function))
|
||||
{
|
||||
var property = agentService.RenderFunctionProperty(agent, function);
|
||||
(options.Tools ??= []).Add(new NopAIFunction(new(function.Name)
|
||||
{
|
||||
Description = function.Description,
|
||||
Parameters = property?.Properties.RootElement.Deserialize<Dictionary<string, object?>>()?.Select(p => new AIFunctionParameterMetadata(p.Key)
|
||||
{
|
||||
Schema = p.Value,
|
||||
}).ToList() ?? [],
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Configure messages
|
||||
List<ChatMessage> messages = [];
|
||||
|
||||
if (_services.GetRequiredService<IAgentService>().RenderedInstruction(agent) is string instruction &&
|
||||
instruction.Length > 0)
|
||||
{
|
||||
messages.Add(new(ChatRole.System, instruction));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(agent.Knowledges))
|
||||
{
|
||||
messages.Add(new(ChatRole.System, agent.Knowledges));
|
||||
}
|
||||
|
||||
foreach (var sample in ProviderHelper.GetChatSamples(agent.Samples))
|
||||
{
|
||||
messages.Add(new(sample.Role == AgentRole.Assistant ? ChatRole.Assistant : ChatRole.User, sample.Content));
|
||||
}
|
||||
|
||||
var fileStorage = _services.GetService<IFileStorageService>();
|
||||
bool allowMultiModal = fileStorage is not null && _services.GetService<ILlmProviderService>()?.GetSetting(Provider, _model ?? "default")?.MultiModal is true;
|
||||
foreach (var x in conversations)
|
||||
{
|
||||
if (x.Role == AgentRole.Function && x.FunctionName is not null)
|
||||
{
|
||||
messages.Add(new(ChatRole.Assistant,
|
||||
[
|
||||
new FunctionCallContent(x.FunctionName, x.FunctionName, JsonSerializer.Deserialize<Dictionary<string, object?>>(x.FunctionArgs ?? "{}")),
|
||||
new FunctionResultContent(x.FunctionName, x.FunctionName, x.Content)
|
||||
]));
|
||||
}
|
||||
else if (x.Role == AgentRole.System || x.Role == AgentRole.Assistant)
|
||||
{
|
||||
messages.Add(new(x.Role == AgentRole.System ? ChatRole.System : ChatRole.Assistant, x.Content));
|
||||
}
|
||||
else if (x.Role == AgentRole.User)
|
||||
{
|
||||
List<AIContent> contents = [new TextContent(!string.IsNullOrWhiteSpace(x.Payload) ? x.Payload : x.Content)];
|
||||
if (allowMultiModal)
|
||||
{
|
||||
foreach (var file in x.Files)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(file.FileData))
|
||||
{
|
||||
contents.Add(new ImageContent(file.FileData));
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(file.FileStorageUrl))
|
||||
{
|
||||
var contentType = FileUtility.GetFileContentType(file.FileStorageUrl);
|
||||
var bytes = fileStorage!.GetFileBytes(file.FileStorageUrl);
|
||||
contents.Add(new ImageContent(bytes, contentType));
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(file.FileUrl))
|
||||
{
|
||||
contents.Add(new ImageContent(file.FileUrl));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
messages.Add(new(ChatRole.User, contents) { AuthorName = x.FunctionName });
|
||||
}
|
||||
}
|
||||
|
||||
var completion = await _client.CompleteAsync(messages);
|
||||
|
||||
RoleDialogModel result = new(AgentRole.Assistant, string.Concat(completion.Message.Contents.OfType<TextContent>()))
|
||||
{
|
||||
CurrentAgentId = agent.Id
|
||||
};
|
||||
|
||||
if (completion.Message.Contents.OfType<FunctionCallContent>().FirstOrDefault() is { } fcc)
|
||||
{
|
||||
result.Role = AgentRole.Function;
|
||||
result.MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty;
|
||||
result.FunctionName = fcc.Name;
|
||||
result.FunctionArgs = fcc.Arguments is not null ? JsonSerializer.Serialize(fcc.Arguments) : null;
|
||||
}
|
||||
|
||||
// After chat completion hook
|
||||
await Task.WhenAll(hooks.Select(hook => hook.AfterGenerated(result, new() { Model = _model ?? "default" })));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<bool> GetChatCompletionsAsync(Agent agent, List<RoleDialogModel> conversations, Func<RoleDialogModel, Task> onMessageReceived, Func<RoleDialogModel, Task> onFunctionExecuting) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<bool> GetChatCompletionsStreamingAsync(Agent agent, List<RoleDialogModel> conversations, Func<RoleDialogModel, Task> onMessageReceived) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
private sealed class NopAIFunction(AIFunctionMetadata metadata) : AIFunction
|
||||
{
|
||||
public override AIFunctionMetadata Metadata { get; } = metadata;
|
||||
|
||||
protected override Task<object?> InvokeCoreAsync(IEnumerable<KeyValuePair<string, object?>> arguments, CancellationToken cancellationToken) =>
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
using BotSharp.Abstraction.MLTasks;
|
||||
using BotSharp.Abstraction.Plugins;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace BotSharp.Plugin.MicrosoftExtensionsAI;
|
||||
|
||||
/// <summary>
|
||||
/// Use Microsoft.Extensions.AI as BotSharp plugin
|
||||
/// </summary>
|
||||
public sealed class MicrosoftExtensionsAIPlugin : IBotSharpPlugin
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public string Id => "B7F2AB8D-1BBA-41CE-9642-2D5E6B5F86A0";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Name => "Microsoft.Extensions.AI";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Description => "Microsoft.Extensions.AI Service";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RegisterDI(IServiceCollection services, IConfiguration config)
|
||||
{
|
||||
services.AddScoped<ITextCompletion, MicrosoftExtensionsAITextCompletionProvider>();
|
||||
services.AddScoped<IChatCompletion, MicrosoftExtensionsAIChatCompletionProvider>();
|
||||
services.AddScoped<ITextEmbedding, MicrosoftExtensionsAITextEmbeddingProvider>();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
using BotSharp.Abstraction.Agents.Enums;
|
||||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Conversations;
|
||||
using BotSharp.Abstraction.Conversations.Models;
|
||||
using BotSharp.Abstraction.Loggers;
|
||||
using BotSharp.Abstraction.MLTasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BotSharp.Plugin.MicrosoftExtensionsAI;
|
||||
|
||||
/// <summary>
|
||||
/// Provides an implementation of <see cref="ITextCompletion"/> for Microsoft.Extensions.AI.
|
||||
/// </summary>
|
||||
public sealed class MicrosoftExtensionsAITextCompletionProvider : ITextCompletion
|
||||
{
|
||||
private readonly IChatClient _chatClient;
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ITokenStatistics _tokenStatistics;
|
||||
private string? _model = null;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Provider => "microsoft-extensions-ai";
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of the <see cref="MicrosoftExtensionsAITextCompletionProvider"/> class.
|
||||
/// </summary>
|
||||
public MicrosoftExtensionsAITextCompletionProvider(
|
||||
IChatClient chatClient,
|
||||
IServiceProvider services,
|
||||
ITokenStatistics tokenStatistics)
|
||||
{
|
||||
_chatClient = chatClient;
|
||||
_services = services;
|
||||
_tokenStatistics = tokenStatistics;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<string> GetCompletion(string text, string agentId, string messageId)
|
||||
{
|
||||
var hooks = _services.GetServices<IContentGeneratingHook>().ToArray();
|
||||
|
||||
// Before chat completion hook
|
||||
Agent agent = new() { Id = agentId };
|
||||
RoleDialogModel userMessage = new(AgentRole.User, text) { MessageId = messageId };
|
||||
await Task.WhenAll(hooks.Select(hook => hook.BeforeGenerating(agent, [userMessage])));
|
||||
|
||||
_tokenStatistics.StartTimer();
|
||||
var completion = await _chatClient.CompleteAsync(text);
|
||||
var result = string.Concat(completion.Message.Contents.OfType<TextContent>());
|
||||
_tokenStatistics.StopTimer();
|
||||
|
||||
// After chat completion hook
|
||||
await Task.WhenAll(hooks.Select(hook =>
|
||||
hook.AfterGenerated(new(AgentRole.Assistant, result), new() { Model = _model ?? "default" })));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetModelName(string model)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(model))
|
||||
{
|
||||
_model = model;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
using BotSharp.Abstraction.MLTasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BotSharp.Plugin.MicrosoftExtensionsAI;
|
||||
|
||||
/// <summary>
|
||||
/// Provides an implementation of <see cref="ITextEmbedding"/> for Microsoft.Extensions.AI.
|
||||
/// </summary>
|
||||
public sealed class MicrosoftExtensionsAITextEmbeddingProvider : ITextEmbedding
|
||||
{
|
||||
private readonly IEmbeddingGenerator<string, Embedding<float>> _generator;
|
||||
private string? _model;
|
||||
private int? _dimensions;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of the <see cref="MicrosoftExtensionsAITextEmbeddingProvider"/> class.
|
||||
/// </summary>
|
||||
public MicrosoftExtensionsAITextEmbeddingProvider(IEmbeddingGenerator<string, Embedding<float>> generator) =>
|
||||
_generator = generator;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Provider => "microsoft-extensions-ai";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<float[]> GetVectorAsync(string text) =>
|
||||
(await _generator.GenerateEmbeddingVectorAsync(text, CreateOptions())).ToArray();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<float[]>> GetVectorsAsync(List<string> texts)
|
||||
{
|
||||
var embeddings = await _generator.GenerateAsync(texts, CreateOptions());
|
||||
return embeddings.Select(e => e.Vector.ToArray()).ToList();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetModelName(string model) => _model = model;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetDimension(int dimension)
|
||||
{
|
||||
if (dimension > 0)
|
||||
{
|
||||
_dimensions = dimension;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int GetDimension() => _dimensions ?? 0;
|
||||
|
||||
private EmbeddingGenerationOptions CreateOptions() =>
|
||||
new()
|
||||
{
|
||||
ModelId = _model,
|
||||
Dimensions = _dimensions,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
using BotSharp.Abstraction.Conversations.Models;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BotSharp.Plugin.MicrosoftExtensionsAI;
|
||||
|
||||
internal static class ProviderHelper
|
||||
{
|
||||
public static IEnumerable<RoleDialogModel> GetChatSamples(List<string> lines)
|
||||
{
|
||||
foreach (string line in lines)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(line))
|
||||
{
|
||||
int pos = line.IndexOf(' ');
|
||||
if (pos > 0)
|
||||
{
|
||||
string role = line.Substring(0, pos - 1).Trim();
|
||||
if (role != "##") // skip comments
|
||||
{
|
||||
yield return new RoleDialogModel(role, line.Substring(line.IndexOf(' ') + 1).Trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -36,13 +36,13 @@ public class PrimaryStagePlanFn : IFunctionCallback
|
|||
{
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
var k = await hook.GetRelevantKnowledges(message, question);
|
||||
var k = await hook.GetDomainKnowledges(message, question);
|
||||
knowledges.AddRange(k);
|
||||
}
|
||||
}
|
||||
knowledges = knowledges.Distinct().ToList();
|
||||
var knowledgeState = String.Join("\r\n", knowledges);
|
||||
state.SetState("relevant_knowledges", knowledgeState);
|
||||
state.SetState("domain_knowledges", knowledgeState);
|
||||
|
||||
// Get first stage planning prompt
|
||||
var currentAgent = await agentService.LoadAgent(message.CurrentAgentId);
|
||||
|
|
@ -64,7 +64,7 @@ public class PrimaryStagePlanFn : IFunctionCallback
|
|||
return true;
|
||||
}
|
||||
|
||||
private async Task<string> GetFirstStagePlanPrompt(RoleDialogModel message, string taskDescription, List<string> relevantKnowledges)
|
||||
private async Task<string> GetFirstStagePlanPrompt(RoleDialogModel message, string taskDescription, List<string> domainKnowledges)
|
||||
{
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var render = _services.GetRequiredService<ITemplateRender>();
|
||||
|
|
@ -86,7 +86,7 @@ public class PrimaryStagePlanFn : IFunctionCallback
|
|||
{
|
||||
{ "task_description", taskDescription },
|
||||
{ "global_knowledges", globalKnowledges },
|
||||
{ "relevant_knowledges", relevantKnowledges },
|
||||
{ "domain_knowledges", domainKnowledges },
|
||||
{ "response_format", responseFormat }
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,15 +36,15 @@ public class SecondaryStagePlanFn : IFunctionCallback
|
|||
var knowledges = new List<string>();
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
var k = await hook.GetRelevantKnowledges(message, taskSecondary.SolutionQuestion);
|
||||
var k = await hook.GetDomainKnowledges(message, taskSecondary.SolutionQuestion);
|
||||
knowledges.AddRange(k);
|
||||
}
|
||||
knowledges = knowledges.Distinct().ToList();
|
||||
var knowledgeResults = string.Join("\r\n\r\n=====\r\n", knowledges);
|
||||
|
||||
var knowledgeState = states.GetState("relevant_knowledges");
|
||||
var knowledgeState = states.GetState("domain_knowledges");
|
||||
knowledgeState += String.Join("\r\n", knowledges);
|
||||
states.SetState("relevant_knowledges", knowledgeState);
|
||||
states.SetState("domain_knowledges", knowledgeState);
|
||||
|
||||
// Get second stage planning prompt
|
||||
var currentAgent = await agentService.LoadAgent(message.CurrentAgentId);
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@ public class SummaryPlanFn : IFunctionCallback
|
|||
var steps = states.GetState("planning_result").JsonArrayContent<SecondStagePlan>();
|
||||
var allTables = new List<string>();
|
||||
var ddlStatements = string.Empty;
|
||||
var relevantKnowledge = states.GetState("planning_result");
|
||||
relevantKnowledge += "\r\n" + states.GetState("relevant_knowledges");
|
||||
var domainKnowledge = states.GetState("planning_result");
|
||||
domainKnowledge += "\r\n" + states.GetState("domain_knowledges");
|
||||
var dictionaryItems = states.GetState("dictionary_items");
|
||||
var excelImportResult = states.GetState("excel_import_result");
|
||||
|
||||
|
|
@ -55,7 +55,7 @@ public class SummaryPlanFn : IFunctionCallback
|
|||
ddlStatements += "\r\n" + msgCopy.Content;
|
||||
|
||||
// Summarize and generate query
|
||||
var prompt = await GetSummaryPlanPrompt(msgCopy, taskRequirement, relevantKnowledge, dictionaryItems, ddlStatements, excelImportResult);
|
||||
var prompt = await GetSummaryPlanPrompt(msgCopy, taskRequirement, domainKnowledge, dictionaryItems, ddlStatements, excelImportResult);
|
||||
_logger.LogInformation($"Summary plan prompt:\r\n{prompt}");
|
||||
|
||||
var plannerAgent = new Agent
|
||||
|
|
@ -76,7 +76,7 @@ public class SummaryPlanFn : IFunctionCallback
|
|||
return true;
|
||||
}
|
||||
|
||||
private async Task<string> GetSummaryPlanPrompt(RoleDialogModel message, string taskDescription, string relevantKnowledge, string dictionaryItems, string ddlStatement, string excelImportResult)
|
||||
private async Task<string> GetSummaryPlanPrompt(RoleDialogModel message, string taskDescription, string domainKnowledge, string dictionaryItems, string ddlStatement, string excelImportResult)
|
||||
{
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var render = _services.GetRequiredService<ITemplateRender>();
|
||||
|
|
@ -104,7 +104,7 @@ public class SummaryPlanFn : IFunctionCallback
|
|||
{ "task_description", taskDescription },
|
||||
{ "summary_requirements", string.Join("\r\n", additionalRequirements) },
|
||||
{ "global_knowledges", globalKnowledges },
|
||||
{ "relevant_knowledges", relevantKnowledge },
|
||||
{ "domain_knowledges", domainKnowledge },
|
||||
{ "dictionary_items", dictionaryItems },
|
||||
{ "table_structure", ddlStatement },
|
||||
{ "excel_import_result", excelImportResult }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
You are a Task Planner. you will breakdown user business requirements into excutable sub-tasks.
|
||||
|
||||
Thinking process:
|
||||
1. Reference to "Task Knowledge" if there is relevant knowledge;
|
||||
1. Reference to "Task Knowledge" if there is domain knowledge;
|
||||
2. Breakdown task into subtasks.
|
||||
- The subtask should contain all needed parameters for subsequent steps.
|
||||
- If limited information provided and there are furture information needed, or miss relationship between steps, set the need_breakdown_task to true.
|
||||
|
|
@ -25,10 +25,10 @@ Global Knowledge:
|
|||
{% endfor %}
|
||||
{%- endif %}
|
||||
|
||||
{% if relevant_knowledges != empty -%}
|
||||
{% if domain_knowledges != empty -%}
|
||||
=====
|
||||
Task Knowledge:
|
||||
{% for k in relevant_knowledges %}
|
||||
{% for k in domain_knowledges %}
|
||||
{{ k }}
|
||||
{% endfor %}
|
||||
{%- endif %}
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ Global Knowledges:
|
|||
{{ global_knowledges }}
|
||||
|
||||
=====
|
||||
Relevant Knowledges:
|
||||
{{ relevant_knowledges }}
|
||||
Domain Knowledges:
|
||||
{{ domain_knowledges }}
|
||||
|
||||
=====
|
||||
Dictionary Items:
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ public class TwilioVoiceController : TwilioController
|
|||
/// <exception cref="ArgumentNullException"></exception>
|
||||
[ValidateRequest]
|
||||
[HttpPost("twilio/voice/welcome")]
|
||||
public TwiMLResult InitiateConversation(VoiceRequest request, [FromQuery] string states)
|
||||
public async Task<TwiMLResult> InitiateConversation(VoiceRequest request, [FromQuery] string[] states, [FromQuery] string intent)
|
||||
{
|
||||
if (request?.CallSid == null)
|
||||
{
|
||||
|
|
@ -41,14 +41,35 @@ public class TwilioVoiceController : TwilioController
|
|||
|
||||
string conversationId = $"TwilioVoice_{request.CallSid}";
|
||||
var twilio = _services.GetRequiredService<TwilioService>();
|
||||
var url = $"twilio/voice/{conversationId}/receive/0?states={states}";
|
||||
var response = twilio.ReturnNoninterruptedInstructions(new List<string> { "twilio/welcome.mp3" }, url, true, timeout: 2);
|
||||
VoiceResponse response;
|
||||
if (string.IsNullOrWhiteSpace(intent))
|
||||
{
|
||||
var url = $"twilio/voice/{conversationId}/receive/0?{GenerateStatesParameter(states)}";
|
||||
response = twilio.ReturnNoninterruptedInstructions(new List<string> { "twilio/welcome.mp3" }, url, true, timeout: 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
int seqNum = 0;
|
||||
var messageQueue = _services.GetRequiredService<TwilioMessageQueue>();
|
||||
var sessionManager = _services.GetRequiredService<ITwilioSessionManager>();
|
||||
await sessionManager.StageCallerMessageAsync(conversationId, seqNum, intent);
|
||||
var callerMessage = new CallerMessage()
|
||||
{
|
||||
ConversationId = conversationId,
|
||||
SeqNumber = seqNum,
|
||||
Content = intent,
|
||||
From = request.From,
|
||||
States = ParseStates(states)
|
||||
};
|
||||
await messageQueue.EnqueueAsync(callerMessage);
|
||||
response = new VoiceResponse().Redirect(new Uri($"{_settings.CallbackHost}/twilio/voice/{conversationId}/reply/{seqNum}?{GenerateStatesParameter(states)}"), HttpMethod.Post);
|
||||
}
|
||||
return TwiML(response);
|
||||
}
|
||||
|
||||
[ValidateRequest]
|
||||
[HttpPost("twilio/voice/{conversationId}/receive/{seqNum}")]
|
||||
public async Task<TwiMLResult> ReceiveCallerMessage([FromRoute] string conversationId, [FromRoute] int seqNum, [FromQuery] string states, VoiceRequest request, [FromQuery] int attempts = 1)
|
||||
public async Task<TwiMLResult> ReceiveCallerMessage([FromRoute] string conversationId, [FromRoute] int seqNum, [FromQuery] string[] states, VoiceRequest request, [FromQuery] int attempts = 1)
|
||||
{
|
||||
var twilio = _services.GetRequiredService<TwilioService>();
|
||||
var messageQueue = _services.GetRequiredService<TwilioMessageQueue>();
|
||||
|
|
@ -72,22 +93,13 @@ public class TwilioVoiceController : TwilioController
|
|||
ConversationId = conversationId,
|
||||
SeqNumber = seqNum,
|
||||
Content = messageContent,
|
||||
Digits = request.Digits,
|
||||
From = request.From
|
||||
Digits = request.Digits,
|
||||
From = request.From,
|
||||
States = ParseStates(states)
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(states))
|
||||
{
|
||||
var kvp = states.Split(':');
|
||||
if (kvp.Length == 2)
|
||||
{
|
||||
callerMessage.States.Add(kvp[0], kvp[1]);
|
||||
}
|
||||
}
|
||||
|
||||
await messageQueue.EnqueueAsync(callerMessage);
|
||||
|
||||
response = new VoiceResponse().Redirect(new Uri($"{_settings.CallbackHost}/twilio/voice/{conversationId}/reply/{seqNum}?states={states}"), HttpMethod.Post);
|
||||
response = new VoiceResponse().Redirect(new Uri($"{_settings.CallbackHost}/twilio/voice/{conversationId}/reply/{seqNum}?{GenerateStatesParameter(states)}"), HttpMethod.Post);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -105,11 +117,11 @@ public class TwilioVoiceController : TwilioController
|
|||
speechPaths.Add($"twilio/say-it-again-{Random.Shared.Next(1, 5)}.mp3");
|
||||
speechPaths.Add($"twilio/voice/speeches/{conversationId}/{lastRepy.SpeechFileName}");
|
||||
}
|
||||
response = twilio.ReturnInstructions(speechPaths, $"twilio/voice/{conversationId}/receive/{seqNum}?states={states}", true);
|
||||
response = twilio.ReturnInstructions(speechPaths, $"twilio/voice/{conversationId}/receive/{seqNum}?{GenerateStatesParameter(states)}", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
response = twilio.ReturnInstructions(null, $"twilio/voice/{conversationId}/receive/{seqNum}?states={states}&attempts={++attempts}", true);
|
||||
response = twilio.ReturnInstructions(null, $"twilio/voice/{conversationId}/receive/{seqNum}?{GenerateStatesParameter(states)}&attempts={++attempts}", true);
|
||||
}
|
||||
}
|
||||
return TwiML(response);
|
||||
|
|
@ -118,7 +130,7 @@ public class TwilioVoiceController : TwilioController
|
|||
[ValidateRequest]
|
||||
[HttpPost("twilio/voice/{conversationId}/reply/{seqNum}")]
|
||||
public async Task<TwiMLResult> ReplyCallerMessage([FromRoute] string conversationId, [FromRoute] int seqNum,
|
||||
[FromQuery] string states, VoiceRequest request)
|
||||
[FromQuery] string[] states, VoiceRequest request)
|
||||
{
|
||||
var nextSeqNum = seqNum + 1;
|
||||
var sessionManager = _services.GetRequiredService<ITwilioSessionManager>();
|
||||
|
|
@ -159,7 +171,7 @@ public class TwilioVoiceController : TwilioController
|
|||
{
|
||||
speechPaths.Add($"twilio/hold-on-short-{holdOnIndex}.mp3");
|
||||
}
|
||||
|
||||
|
||||
var fileName = $"indication_{seqNum}_{segIndex}.mp3";
|
||||
fileStorage.SaveSpeechFile(conversationId, fileName, data);
|
||||
speechPaths.Add($"twilio/voice/speeches/{conversationId}/{fileName}");
|
||||
|
|
@ -173,7 +185,7 @@ public class TwilioVoiceController : TwilioController
|
|||
segIndex++;
|
||||
}
|
||||
}
|
||||
response = twilio.ReturnInstructions(speechPaths, $"twilio/voice/{conversationId}/reply/{seqNum}?states={states}", true);
|
||||
response = twilio.ReturnInstructions(speechPaths, $"twilio/voice/{conversationId}/reply/{seqNum}?{GenerateStatesParameter(states)}", true);
|
||||
await sessionManager.RemoveReplyIndicationAsync(conversationId, seqNum);
|
||||
}
|
||||
else
|
||||
|
|
@ -196,7 +208,7 @@ public class TwilioVoiceController : TwilioController
|
|||
instructions.Add($"twilio/typing-{typingIndex}.mp3");
|
||||
}
|
||||
|
||||
response = twilio.ReturnInstructions(instructions, $"twilio/voice/{conversationId}/reply/{seqNum}?states={states}", true);
|
||||
response = twilio.ReturnInstructions(instructions, $"twilio/voice/{conversationId}/reply/{seqNum}?{GenerateStatesParameter(states)}", true);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -214,7 +226,7 @@ public class TwilioVoiceController : TwilioController
|
|||
response = twilio.ReturnInstructions(new List<string>
|
||||
{
|
||||
$"twilio/voice/speeches/{conversationId}/{reply.SpeechFileName}"
|
||||
}, $"twilio/voice/{conversationId}/receive/{nextSeqNum}?states={states}", true, hints:reply.Hints);
|
||||
}, $"twilio/voice/{conversationId}/receive/{nextSeqNum}?{GenerateStatesParameter(states)}", true, hints: reply.Hints);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -233,4 +245,31 @@ public class TwilioVoiceController : TwilioController
|
|||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
private Dictionary<string, string> ParseStates(string[] states)
|
||||
{
|
||||
var result = new Dictionary<string, string>();
|
||||
if (states is null || !states.Any())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
foreach (var kvp in states)
|
||||
{
|
||||
var parts = kvp.Split(':', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
result.Add(parts[0], parts[1]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private string GenerateStatesParameter(string[] states)
|
||||
{
|
||||
if (states is null || states.Length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return string.Join("&", states.Select(x => $"states={x}"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue