Merge branch 'SciSharp:master' into master

This commit is contained in:
Haiping 2024-10-18 14:20:10 -05:00 committed by GitHub
commit a6ce8ea9b5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 408 additions and 258 deletions

View file

@ -4,5 +4,23 @@ namespace BotSharp.Abstraction.Instructs;
public interface IInstructService
{
/// <summary>
/// Execute completion by using specified instruction or template
/// </summary>
/// <param name="agentId">Agent (static agent)</param>
/// <param name="message">Additional message provided by user</param>
/// <param name="templateName">Template name</param>
/// <param name="instruction">System prompt</param>
/// <returns></returns>
Task<InstructResult> Execute(string agentId, RoleDialogModel message, string? templateName = null, string? instruction = null);
/// <summary>
/// A generic way to execute completion by using specified instruction or template
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="instruction">Prompt</param>
/// <param name="agentId">Agent id</param>
/// <param name="options">Llm Provider, model, message, prompt data</param>
/// <returns></returns>
Task<T?> Instruct<T>(string instruction, string agentId, InstructOptions options) where T : class;
}

View file

@ -0,0 +1,29 @@
namespace BotSharp.Abstraction.Instructs.Models;
public class InstructOptions
{
/// <summary>
/// Llm provider
/// </summary>
public string Provider { get; set; } = null!;
/// <summary>
/// Llm model
/// </summary>
public string Model { get; set; } = null!;
/// <summary>
/// Conversation id. When this field is not null, it will get dialogs from conversation.
/// </summary>
public string? ConversationId { get; set; }
/// <summary>
/// The single message. It can be append to the whole dialogs or sent alone.
/// </summary>
public string? Message { get; set; }
/// <summary>
/// Data to fill in prompt
/// </summary>
public Dictionary<string, object> Data { get; set; } = new();
}

View file

@ -40,10 +40,10 @@ public partial class ConversationService
routing.Context.Push(agent.Id, reason: "request started");
// Save payload in order to assign the payload before hook is invoked
if (replyMessage != null && !string.IsNullOrEmpty(replyMessage.Payload))
{
message.Payload = replyMessage.Payload;
}
// if (replyMessage != null && !string.IsNullOrEmpty(replyMessage.Payload))
// {
// message.Payload = replyMessage.Payload;
// }
// Before chat completion hook
hooks = ReOrderConversationHooks(hooks);

View file

@ -372,10 +372,10 @@ public class ConversationStateService : IConversationStateService, IDisposable
private bool CheckArgType(string name, string value)
{
var agentTypes = AgentService.AgentParameterTypes.SelectMany(p => p.Value).ToList();
var filed = agentTypes.FirstOrDefault(t => t.Key == name);
if (filed.Key != null)
var found = agentTypes.FirstOrDefault(t => t.Key == name);
if (found.Key != null)
{
return filed.Value switch
return found.Value switch
{
"boolean" => bool.TryParse(value, out _),
"number" => long.TryParse(value, out _),

View file

@ -0,0 +1,100 @@
using BotSharp.Abstraction.Instructs;
using BotSharp.Abstraction.Instructs.Models;
using BotSharp.Abstraction.MLTasks;
namespace BotSharp.Core.Instructs;
public partial class InstructService
{
public async Task<InstructResult> Execute(string agentId, RoleDialogModel message, string? templateName = null, string? instruction = null)
{
var agentService = _services.GetRequiredService<IAgentService>();
Agent agent = await agentService.LoadAgent(agentId);
if (agent.Disabled)
{
var content = $"This agent ({agent.Name}) is disabled, please install the corresponding plugin ({agent.Plugin.Name}) to activate this agent.";
return new InstructResult
{
MessageId = message.MessageId,
Text = content
};
}
// Trigger before completion hooks
var hooks = _services.GetServices<IInstructHook>();
foreach (var hook in hooks)
{
if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId)
{
continue;
}
await hook.BeforeCompletion(agent, message);
// Interrupted by hook
if (message.StopCompletion)
{
return new InstructResult
{
MessageId = message.MessageId,
Text = message.Content
};
}
}
// Render prompt
var prompt = string.IsNullOrEmpty(templateName) ?
agentService.RenderedInstruction(agent) :
agentService.RenderedTemplate(agent, templateName);
var completer = CompletionProvider.GetCompletion(_services,
agentConfig: agent.LlmConfig);
var response = new InstructResult
{
MessageId = message.MessageId
};
if (completer is ITextCompletion textCompleter)
{
var result = await textCompleter.GetCompletion(prompt, agentId, message.MessageId);
response.Text = result;
}
else if (completer is IChatCompletion chatCompleter)
{
if (instruction == "#TEMPLATE#")
{
instruction = prompt;
prompt = message.Content;
}
var result = await chatCompleter.GetChatCompletions(new Agent
{
Id = agentId,
Name = agent.Name,
Instruction = instruction
}, new List<RoleDialogModel>
{
new RoleDialogModel(AgentRole.User, prompt)
{
CurrentAgentId = agentId,
MessageId = message.MessageId
}
});
response.Text = result.Content;
}
foreach (var hook in hooks)
{
if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId)
{
continue;
}
await hook.AfterCompletion(agent, response);
}
return response;
}
}

View file

@ -0,0 +1,109 @@
using BotSharp.Abstraction.Instructs.Models;
using BotSharp.Abstraction.Templating;
using System.Collections;
using System.Reflection;
namespace BotSharp.Core.Instructs;
public partial class InstructService
{
public async Task<T?> Instruct<T>(string instruction, string agentId, InstructOptions options) where T : class
{
var prompt = GetPrompt(instruction, options.Data);
var response = await GetAiResponse(agentId, prompt, options);
if (string.IsNullOrWhiteSpace(response.Content)) return null;
var type = typeof(T);
T? result = null;
try
{
if (IsStringType(type))
{
result = response.Content as T;
}
else if (IsListType(type))
{
var text = response.Content.JsonArrayContent();
if (!string.IsNullOrWhiteSpace(text))
{
result = JsonSerializer.Deserialize<T>(text, _options.JsonSerializerOptions);
}
}
else
{
var text = response.Content.JsonContent();
if (!string.IsNullOrWhiteSpace(text))
{
result = JsonSerializer.Deserialize<T>(text, _options.JsonSerializerOptions);
}
}
}
catch (Exception ex)
{
_logger.LogWarning($"Error when getting ai response, {ex.Message}\r\n{ex.InnerException}");
}
return result;
}
private string GetPrompt(string instruction, Dictionary<string, object> data)
{
var render = _services.GetRequiredService<ITemplateRender>();
return render.Render(instruction, data ?? new Dictionary<string, object>());
}
private async Task<RoleDialogModel> GetAiResponse(string agentId, string prompt, InstructOptions options)
{
var agentService = _services.GetRequiredService<IAgentService>();
var agent = await agentService.LoadAgent(agentId);
var localAgent = new Agent
{
Id = agentId,
Name = agent?.Name ?? "Unknown",
Instruction = prompt,
TemplateDict = new()
};
var messages = BuildDialogs(options);
var completion = CompletionProvider.GetChatCompletion(_services, provider: options.Provider, model: options.Model);
return await completion.GetChatCompletions(localAgent, messages);
}
private List<RoleDialogModel> BuildDialogs(InstructOptions options)
{
var messages = new List<RoleDialogModel>();
if (!string.IsNullOrWhiteSpace(options.ConversationId))
{
var conv = _services.GetRequiredService<IConversationService>();
var dialogs = conv.GetDialogHistory();
messages.AddRange(dialogs);
}
if (!string.IsNullOrWhiteSpace(options.Message))
{
messages.Add(new RoleDialogModel(AgentRole.User, options.Message));
}
return messages;
}
private bool IsStringType(Type? type)
{
if (type == null) return false;
return type == typeof(string);
}
private bool IsListType(Type? type)
{
if (type == null) return false;
var interfaces = type.GetTypeInfo().ImplementedInterfaces;
return type.IsArray || interfaces.Any(x => x.Name == typeof(IEnumerable).Name);
}
}

View file

@ -1,118 +1,21 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Instructs;
using BotSharp.Abstraction.Instructs.Models;
using BotSharp.Abstraction.MLTasks;
using BotSharp.Abstraction.Options;
namespace BotSharp.Core.Instructs;
public partial class InstructService : IInstructService
{
private readonly IServiceProvider _services;
private readonly ILogger _logger;
private readonly BotSharpOptions _options;
private readonly ILogger<InstructService> _logger;
public InstructService(IServiceProvider services, ILogger<InstructService> logger)
public InstructService(
IServiceProvider services,
BotSharpOptions options,
ILogger<InstructService> logger)
{
_services = services;
_options = options;
_logger = logger;
}
/// <summary>
/// Execute completion by using specified instruction or template
/// </summary>
/// <param name="agentId">Agent (static agent)</param>
/// <param name="message">Additional message provided by user</param>
/// <param name="templateName">Template name</param>
/// <param name="instruction">System prompt</param>
/// <returns></returns>
public async Task<InstructResult> Execute(string agentId, RoleDialogModel message, string? templateName = null, string? instruction = null)
{
var agentService = _services.GetRequiredService<IAgentService>();
Agent agent = await agentService.LoadAgent(agentId);
if (agent.Disabled)
{
var content = $"This agent ({agent.Name}) is disabled, please install the corresponding plugin ({agent.Plugin.Name}) to activate this agent.";
return new InstructResult
{
MessageId = message.MessageId,
Text = content
};
}
// Trigger before completion hooks
var hooks = _services.GetServices<IInstructHook>();
foreach (var hook in hooks)
{
if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId)
{
continue;
}
await hook.BeforeCompletion(agent, message);
// Interrupted by hook
if (message.StopCompletion)
{
return new InstructResult
{
MessageId = message.MessageId,
Text = message.Content
};
}
}
// Render prompt
var prompt = string.IsNullOrEmpty(templateName) ?
agentService.RenderedInstruction(agent) :
agentService.RenderedTemplate(agent, templateName);
var completer = CompletionProvider.GetCompletion(_services,
agentConfig: agent.LlmConfig);
var response = new InstructResult
{
MessageId = message.MessageId
};
if (completer is ITextCompletion textCompleter)
{
var result = await textCompleter.GetCompletion(prompt, agentId, message.MessageId);
response.Text = result;
}
else if (completer is IChatCompletion chatCompleter)
{
if (instruction == "#TEMPLATE#")
{
instruction = prompt;
prompt = message.Content;
}
var result = await chatCompleter.GetChatCompletions(new Agent
{
Id = agentId,
Name = agent.Name,
Instruction = instruction
}, new List<RoleDialogModel>
{
new RoleDialogModel(AgentRole.User, prompt)
{
CurrentAgentId = agentId,
MessageId = message.MessageId
}
});
response.Text = result.Content;
}
foreach (var hook in hooks)
{
if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId)
{
continue;
}
await hook.AfterCompletion(agent, response);
}
return response;
}
}

View file

@ -42,11 +42,10 @@ public class RoutingContext : IRoutingContext
_routerAgentIds = agentService.GetAgents(new AgentFilter
{
Type = AgentType.Routing
}).Result.Items
.Select(x => x.Id).ToArray();
}).Result.Items.Select(x => x.Id).ToArray();
}
return _stack.Where(x => !_routerAgentIds.Contains(x)).Last();
return _stack.Where(x => !_routerAgentIds.Contains(x)).LastOrDefault() ?? string.Empty;
}
}

View file

@ -16,15 +16,7 @@ public partial class RoutingService
role = agent.Name;
}
if (role == AgentRole.User)
{
conversation += $"{role}: {dialog.Payload ?? dialog.Content}\r\n";
}
else
{
// Assistant reply deosn't need help with payload
conversation += $"{role}: {dialog.Content}\r\n";
}
conversation += $"{role}: {dialog.Payload ?? dialog.Content}\r\n";
}
return conversation;

View file

@ -312,7 +312,7 @@ public class ChatCompletionProvider : IChatCompletion
}
else if (message.Role == AgentRole.Assistant)
{
messages.Add(new AssistantChatMessage(message.Content));
messages.Add(new AssistantChatMessage(message.Payload ?? message.Content));
}
}

View file

@ -1 +1,2 @@
Please call handle_excel_request if user wants to load the data from a excel/csv file.
Please call handle_excel_request if user wants to load the data from a excel/csv file.
handle_excel_request can NOT generate excel/csv.

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(TargetFramework)</TargetFramework>
@ -21,7 +21,8 @@
<None Remove="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\functions\confirm_knowledge_persistence.json" />
<None Remove="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\functions\memorize_knowledge.json" />
<None Remove="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\instructions\instruction.liquid" />
<None Remove="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\templates\knowledge.generation.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\templates\knowledge_retrieval.fn.liquid" />
</ItemGroup>
@ -38,7 +39,10 @@
<Content Include="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\instructions\instruction.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\templates\knowledge.generation.liquid">
<Content Include="data\agents\01acc3e5-0af7-49e6-ad7a-a760bd12dc40\templates\knowledge.generation.refine.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<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\knowledge_retrieval.json">

View file

@ -1,65 +0,0 @@
using BotSharp.Abstraction.Templating;
using BotSharp.Core.Infrastructures;
namespace BotSharp.Plugin.KnowledgeBase.Functions;
public class GenerateKnowledgeFn : IFunctionCallback
{
public string Name => "generate_knowledge";
public string Indication => "generating knowledge";
private readonly IServiceProvider _services;
private readonly KnowledgeBaseSettings _settings;
public GenerateKnowledgeFn(IServiceProvider services, KnowledgeBaseSettings settings)
{
_services = services;
_settings = settings;
}
public async Task<bool> Execute(RoleDialogModel message)
{
var args = JsonSerializer.Deserialize<ExtractedKnowledge>(message.FunctionArgs ?? "{}");
var agentService = _services.GetRequiredService<IAgentService>();
var llmAgent = await agentService.GetAgent(BuiltInAgentId.Planner);
var generateKnowledgePrompt = await GetGenerateKnowledgePrompt(args.Question, args.Answer);
var agent = new Agent
{
Id = message.CurrentAgentId ?? string.Empty,
Name = "sqlDriver_DictionarySearch",
Instruction = generateKnowledgePrompt,
LlmConfig = llmAgent.LlmConfig
};
var response = await GetAiResponse(agent);
message.Data = response.Content.JsonArrayContent<ExtractedKnowledge>();
message.Content = response.Content;
return true;
}
private async Task<string> GetGenerateKnowledgePrompt(string userQuestions, string sqlAnswer)
{
var agentService = _services.GetRequiredService<IAgentService>();
var render = _services.GetRequiredService<ITemplateRender>();
var agent = await agentService.GetAgent(BuiltInAgentId.Learner);
var template = agent.Templates.FirstOrDefault(x => x.Name == "knowledge.generation")?.Content ?? string.Empty;
return render.Render(template, new Dictionary<string, object>
{
{ "user_questions", userQuestions },
{ "sql_answer", sqlAnswer },
});
}
private async Task<RoleDialogModel> GetAiResponse(Agent agent)
{
var text = "Generate question and answer pair";
var message = new RoleDialogModel(AgentRole.User, text);
var completion = CompletionProvider.GetChatCompletion(_services,
provider: agent.LlmConfig.Provider,
model: agent.LlmConfig.Model);
return await completion.GetChatCompletions(agent, new List<RoleDialogModel> { message });
}
}

View file

@ -1,4 +1,4 @@
You are a knowledge generator for knowledge base. Extract the answer in "SQL Answer" to answer the User Questions.
You are a knowledge extractor for knowledge base. Extract the answer in "SQL Answer" to answer the User Questions.
* Replace alias with the actual table name. Output json array only, formatting as [{"question":"string", "answer":""}].
* Skip the question/answer for tmp table.
* Don't include tmp table in the answer.

View file

@ -0,0 +1,15 @@
You are a knowledge extractor for knowledge base. Utilize the new answer and existing answer to generate the final integrated answer.
Output json array only, formatting as [{"question":"", "answer":""}]. Replace the new line with \r\n.
* Don't loss any knowledge in the existing answer.
=====
User Question:
{{ user_question }}
=====
New Answer:
{{ new_answer }}
=====
Existing Answer:
{{ existing_answer }}

View file

@ -290,7 +290,7 @@ public class ChatCompletionProvider : IChatCompletion
}
else if (message.Role == AgentRole.Assistant)
{
messages.Add(new AssistantChatMessage(message.Content));
messages.Add(new AssistantChatMessage(message.Payload ?? message.Content));
}
}

View file

@ -6,10 +6,13 @@ public class PrimaryStagePlanFn : IFunctionCallback
{
public string Name => "plan_primary_stage";
public string Indication => "Currently analyzing and breaking down user requirements.";
private readonly IServiceProvider _services;
private readonly ILogger<PrimaryStagePlanFn> _logger;
public PrimaryStagePlanFn(IServiceProvider services, ILogger<PrimaryStagePlanFn> logger)
public PrimaryStagePlanFn(
IServiceProvider services,
ILogger<PrimaryStagePlanFn> logger)
{
_services = services;
_logger = logger;
@ -35,15 +38,17 @@ public class PrimaryStagePlanFn : IFunctionCallback
}
}
knowledges = knowledges.Distinct().ToList();
var knowledgeState = String.Join("\r\n", knowledges);
state.SetState("relevant_knowledges", knowledgeState);
// Get first stage planning prompt
var currentAgent = await agentService.LoadAgent(message.CurrentAgentId);
var firstPlanningPrompt = await GetFirstStagePlanPrompt(message, task.Requirements, knowledges);
var prompt = await GetFirstStagePlanPrompt(message, task.Requirements, knowledges);
var plannerAgent = new Agent
{
Id = BuiltInAgentId.Planner,
Name = "planning_1st",
Instruction = firstPlanningPrompt,
Name = "FirstStagePlanner",
Instruction = prompt,
TemplateDict = new Dictionary<string, object>(),
LlmConfig = currentAgent.LlmConfig
};
@ -64,11 +69,7 @@ public class PrimaryStagePlanFn : IFunctionCallback
var agent = await agentService.GetAgent(BuiltInAgentId.Planner);
var template = agent.Templates.FirstOrDefault(x => x.Name == "two_stage.1st.plan")?.Content ?? string.Empty;
var responseFormat = JsonSerializer.Serialize(new FirstStagePlan
{
Parameters = [ JsonDocument.Parse("{}") ],
Results = [ string.Empty ]
});
var responseFormat = JsonSerializer.Serialize(new FirstStagePlan{});
// Get global knowledges
var globalKnowledges = new List<string>();

View file

@ -1,5 +1,4 @@
using BotSharp.Plugin.Planner.TwoStaging.Models;
using System.Threading.Tasks;
namespace BotSharp.Plugin.Planner.Functions;
@ -7,10 +6,13 @@ public class SecondaryStagePlanFn : IFunctionCallback
{
public string Name => "plan_secondary_stage";
public string Indication => "Further analyzing and breaking down user sub-needs.";
private readonly IServiceProvider _services;
private readonly ILogger<SecondaryStagePlanFn> _logger;
public SecondaryStagePlanFn(IServiceProvider services, ILogger<SecondaryStagePlanFn> logger)
public SecondaryStagePlanFn(
IServiceProvider services,
ILogger<SecondaryStagePlanFn> logger)
{
_services = services;
_logger = logger;
@ -25,7 +27,7 @@ public class SecondaryStagePlanFn : IFunctionCallback
var msgSecondary = RoleDialogModel.From(message);
var collectionName = knowledgeSettings.Default.CollectionName;
var planPrimary = states.GetState("planning_result");
var planResult = states.GetState("planning_result");
var taskSecondary = JsonSerializer.Deserialize<SecondaryBreakdownTask>(msgSecondary.FunctionArgs);
@ -38,19 +40,22 @@ public class SecondaryStagePlanFn : IFunctionCallback
knowledges.AddRange(k);
}
knowledges = knowledges.Distinct().ToList();
var knowledgeResults = string.Join("\r\n\r\n=====\r\n", knowledges);
var knowledgeState = states.GetState("relevant_knowledges");
knowledgeState += String.Join("\r\n", knowledges);
states.SetState("relevant_knowledges", knowledgeState);
// Get second stage planning prompt
var currentAgent = await agentService.LoadAgent(message.CurrentAgentId);
var secondPlanningPrompt = await GetSecondStagePlanPrompt(taskSecondary.TaskDescription, planPrimary, knowledgeResults, message);
_logger.LogInformation(secondPlanningPrompt);
var prompt = await GetSecondStagePlanPrompt(taskSecondary.TaskDescription, planResult, knowledgeResults, message);
_logger.LogInformation(prompt);
var plannerAgent = new Agent
{
Id = BuiltInAgentId.Planner,
Name = "planning_2nd",
Instruction = secondPlanningPrompt,
Name = "SecondStagePlanner",
Instruction = prompt,
TemplateDict = new Dictionary<string, object>(),
LlmConfig = currentAgent.LlmConfig
};
@ -63,7 +68,7 @@ public class SecondaryStagePlanFn : IFunctionCallback
return true;
}
private async Task<string> GetSecondStagePlanPrompt(string taskDescription, string planPrimary, string knowledgeResults, RoleDialogModel message)
private async Task<string> GetSecondStagePlanPrompt(string taskDescription, string planResult, string knowledgeResults, RoleDialogModel message)
{
var agentService = _services.GetRequiredService<IAgentService>();
var render = _services.GetRequiredService<ITemplateRender>();
@ -79,7 +84,7 @@ public class SecondaryStagePlanFn : IFunctionCallback
return render.Render(template, new Dictionary<string, object>
{
{ "task_description", taskDescription },
{ "primary_plan", planPrimary },
{ "primary_plan", planResult },
{ "additional_knowledge", knowledgeResults },
{ "response_format", responseFormat }
});

View file

@ -8,6 +8,7 @@ public class SummaryPlanFn : IFunctionCallback
{
public string Name => "plan_summary";
public string Indication => "Organizing and summarizing the final output results.";
private readonly IServiceProvider _services;
private readonly ILogger<SummaryPlanFn> _logger;
@ -35,6 +36,7 @@ public class SummaryPlanFn : IFunctionCallback
var allTables = new List<string>();
var ddlStatements = string.Empty;
var relevantKnowledge = states.GetState("planning_result");
relevantKnowledge += "\r\n" + states.GetState("relevant_knowledges");
var dictionaryItems = states.GetState("dictionary_items");
var excelImportResult = states.GetState("excel_import_result");
@ -53,14 +55,14 @@ public class SummaryPlanFn : IFunctionCallback
ddlStatements += "\r\n" + msgCopy.Content;
// Summarize and generate query
var summaryPlanPrompt = await GetSummaryPlanPrompt(msgCopy, taskRequirement, relevantKnowledge, dictionaryItems, ddlStatements, excelImportResult);
_logger.LogInformation($"Summary plan prompt:\r\n{summaryPlanPrompt}");
var prompt = await GetSummaryPlanPrompt(msgCopy, taskRequirement, relevantKnowledge, dictionaryItems, ddlStatements, excelImportResult);
_logger.LogInformation($"Summary plan prompt:\r\n{prompt}");
var plannerAgent = new Agent
{
Id = BuiltInAgentId.Planner,
Name = "Planner Summary",
Instruction = summaryPlanPrompt,
Name = "SummaryPlanner",
Instruction = prompt,
LlmConfig = currentAgent.LlmConfig
};
@ -105,7 +107,7 @@ public class SummaryPlanFn : IFunctionCallback
{ "relevant_knowledges", relevantKnowledge },
{ "dictionary_items", dictionaryItems },
{ "table_structure", ddlStatement },
{ "excel_import_result",excelImportResult }
{ "excel_import_result", excelImportResult }
});
}
private async Task<RoleDialogModel> GetAiResponse(Agent plannerAgent)

View file

@ -5,8 +5,8 @@ public class FirstStagePlan
[JsonPropertyName("task_detail")]
public string Task { get; set; } = "";
[JsonPropertyName("reason")]
public string Reason { get; set; } = "";
//[JsonPropertyName("reason")]
//public string Reason { get; set; } = "";
[JsonPropertyName("step")]
public int Step { get; set; } = -1;
@ -20,14 +20,14 @@ public class FirstStagePlan
[JsonPropertyName("related_tables")]
public string[] Tables { get; set; } = [];
[JsonPropertyName("related_urls")]
public string[] Urls { get; set; } = [];
//[JsonPropertyName("related_urls")]
//public string[] Urls { get; set; } = [];
[JsonPropertyName("input_args")]
public JsonDocument[] Parameters { get; set; } = [];
//[JsonPropertyName("input_args")]
//public JsonDocument[] Parameters { get; set; } = [];
[JsonPropertyName("output_results")]
public string[] Results { get; set; } = [];
//[JsonPropertyName("output_results")]
//public string[] Results { get; set; } = [];
public override string ToString()
{

View file

@ -17,12 +17,12 @@
</ItemGroup>
<ItemGroup>
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\sql_dictionary_lookup.json" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\sql_select.json" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\sql_table_definition.json" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\sql_dictionary_lookup.fn.liquid" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\verify_dictionary_term.json" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\sql_executor.fn.liquid" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\sql_table_definition.fn.liquid" />
<None Remove="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\verify_dictionary_term.fn.liquid" />
<None Remove="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\agent.json" />
<None Remove="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\functions\execute_sql.json" />
<None Remove="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\functions\lookup_dictionary.json" />
@ -37,10 +37,10 @@
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\sql_table_definition.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\sql_dictionary_lookup.json">
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\verify_dictionary_term.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\sql_dictionary_lookup.fn.liquid">
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\verify_dictionary_term.fn.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\beda4c12-e1ec-4b4b-b328-3df4a6687c4f\functions\sql_table_definition.json">

View file

@ -99,6 +99,11 @@ public class ExecuteQueryFn : IFunctionCallback
private async Task<ExecuteQueryArgs> RefineSqlStatement(RoleDialogModel message, ExecuteQueryArgs args)
{
if (args.Tables == null || args.Tables.Length == 0)
{
return args;
}
// get table DDL
var fn = _services.GetRequiredService<IRoutingService>();
var msg = RoleDialogModel.From(message);

View file

@ -7,12 +7,15 @@ using static Dapper.SqlMapper;
namespace BotSharp.Plugin.SqlDriver.Functions;
public class LookupDictionaryFn : IFunctionCallback
public class VerifyDictionaryTerm : IFunctionCallback
{
public string Name => "verify_dictionary_term";
public string Indication => "Verifying dictionary term";
private readonly IServiceProvider _services;
public LookupDictionaryFn(IServiceProvider services)
public VerifyDictionaryTerm(IServiceProvider services)
{
_services = services;
}

View file

@ -1,6 +1,6 @@
{
"name": "verify_dictionary_term",
"description": "Get id from dictionary table by keyword. Call this function only if need_lookup_dictionary is True",
"description": "Get id from dictionary table by keyword. Call this function only if need_lookup_dictionary is true and is_insert is false",
"parameters": {
"type": "object",
"properties": {
@ -12,6 +12,10 @@
"type": "string",
"description": "the reason why you need to call verify_dictionary_term"
},
"is_insert": {
"type": "boolean",
"description": "if SQL statement is inserting."
},
"tables": {
"type": "array",
"description": "all related tables",

View file

@ -20,13 +20,13 @@
"tables": {
"type": "array",
"description": "all related tables",
"description": "all related tables in the sql statements",
"items": {
"type": "string",
"description": "table name"
}
}
},
"required": [ "sql_statement", "tables", "formatting_result" ]
"required": [ "sql_statements", "tables", "formatting_result" ]
}
}

View file

@ -1,5 +1,5 @@
Output in human readable format. If there is large amount of rows, shape it in tabular, otherwise, output in plain text.
Put user task description in the first line in the same language, for example, user is using Chinese, you have to output the result in Chinese.
Put user task description in the first line in the same language, for example, if user is using Chinese, you have to output the result in Chinese.
User Task Description:
{{ requirement_detail }}

View file

@ -72,6 +72,7 @@ public class TwilioVoiceController : TwilioController
ConversationId = conversationId,
SeqNumber = seqNum,
Content = messageContent,
Digits = request.Digits,
From = request.From
};

View file

@ -5,6 +5,7 @@ namespace BotSharp.Plugin.Twilio.Models
public string ConversationId { get; set; }
public int SeqNumber { get; set; }
public string Content { get; set; }
public string Digits { get; set; }
public string From { get; set; }
public Dictionary<string, string> States { get; set; } = new();

View file

@ -66,23 +66,11 @@ namespace BotSharp.Plugin.Twilio.Services
var sessionManager = sp.GetRequiredService<ITwilioSessionManager>();
var progressService = sp.GetRequiredService<IConversationProgressService>();
InitProgressService(message, sessionManager, progressService);
routing.Context.SetMessageId(message.ConversationId, inputMsg.MessageId);
var states = new List<MessageState>
{
new MessageState("channel", ConversationChannel.Phone),
new MessageState("calling_phone", message.From)
};
foreach (var kvp in message.States)
{
states.Add(new MessageState(kvp.Key, kvp.Value));
}
conv.SetConversationId(message.ConversationId, states);
InitConversation(message, inputMsg, conv, routing);
var result = await conv.SendMessage(config.AgentId,
inputMsg,
replyMessage: null,
replyMessage: BuildPostbackMessageModel(conv, message),
async msg =>
{
reply = new AssistantMessage()
@ -94,13 +82,50 @@ namespace BotSharp.Plugin.Twilio.Services
};
}
);
reply.SpeechFileName = await GetReplySpeechFileName(message.ConversationId, reply, sp);
reply.Hints = GetHints(reply);
reply.Content = null;
await sessionManager.SetAssistantReplyAsync(message.ConversationId, message.SeqNumber, reply);
}
private PostbackMessageModel BuildPostbackMessageModel(IConversationService conv, CallerMessage message)
{
var messages = conv.GetDialogHistory(1);
if (!messages.Any()) return null;
var lastMessage = messages[0];
if (string.IsNullOrEmpty(lastMessage.PostbackFunctionName)) return null;
return new PostbackMessageModel
{
FunctionName = lastMessage.PostbackFunctionName,
ParentId = lastMessage.MessageId,
Payload = message.Digits
};
}
private static void InitConversation(CallerMessage message, RoleDialogModel inputMsg, IConversationService conv, IRoutingService routing)
{
routing.Context.SetMessageId(message.ConversationId, inputMsg.MessageId);
var states = new List<MessageState>
{
new("channel", ConversationChannel.Phone),
new("calling_phone", message.From)
};
states.AddRange(message.States.Select(kvp => new MessageState(kvp.Key, kvp.Value)));
conv.SetConversationId(message.ConversationId, states);
}
private static async Task<string> GetReplySpeechFileName(string conversationId, AssistantMessage reply, IServiceProvider sp)
{
var completion = CompletionProvider.GetAudioCompletion(sp, "openai", "tts-1");
var fileStorage = sp.GetRequiredService<IFileStorageService>();
var data = await completion.GenerateAudioFromTextAsync(reply.Content);
var fileName = $"reply_{reply.MessageId}.mp3";
fileStorage.SaveSpeechFile(message.ConversationId, fileName, data);
reply.SpeechFileName = fileName;
fileStorage.SaveSpeechFile(conversationId, fileName, data);
return fileName;
}
private static string GetHints(AssistantMessage reply)
{
var phrases = reply.Content.Split(',', StringSplitOptions.RemoveEmptyEntries);
int capcity = 100;
var hints = new List<string>(capcity);
@ -122,9 +147,7 @@ namespace BotSharp.Plugin.Twilio.Services
}
// add frequency short words
hints.AddRange(["yes", "no", "correct", "right"]);
reply.Hints = string.Join(", ", hints.Select(x => x.ToLower()).Distinct().Reverse());
reply.Content = null;
await sessionManager.SetAssistantReplyAsync(message.ConversationId, message.SeqNumber, reply);
return string.Join(", ", hints.Select(x => x.ToLower()).Distinct().Reverse());
}
private static void InitProgressService(CallerMessage message, ITwilioSessionManager sessionManager, IConversationProgressService progressService)