GetChatCompletionsAsyncRecursively
This commit is contained in:
parent
f825438632
commit
ca60476d24
|
|
@ -80,6 +80,7 @@ The main documentation for the site is organized into the following sections:
|
|||
:caption: Prompt Engineering
|
||||
|
||||
prompt/intro
|
||||
prompt/template
|
||||
|
||||
.. _architecture-docs:
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
# Prompt Engineering
|
||||
# Prompt Engineering
|
||||
|
||||
LLM uses prompt as input, and the model produces different outputs according to the input.
|
||||
3
docs/prompt/template.md
Normal file
3
docs/prompt/template.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Template
|
||||
|
||||
We can define the prompt as a template, and the template can be changed according to variables, so that a instruction file can be used to generate a dynamic prompt.
|
||||
|
|
@ -5,6 +5,6 @@ namespace BotSharp.Abstraction.Conversations;
|
|||
public interface IConversationStorage
|
||||
{
|
||||
void InitStorage(string conversationId);
|
||||
void Append(string conversationId, RoleDialogModel dialog);
|
||||
void Append(string conversationId, Agent agent, RoleDialogModel dialog);
|
||||
List<RoleDialogModel> GetDialogs(string conversationId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using BotSharp.Abstraction.Agents.Enums;
|
||||
|
||||
namespace BotSharp.Abstraction.Conversations.Models;
|
||||
|
||||
public class RoleDialogModel
|
||||
|
|
@ -40,6 +42,13 @@ public class RoleDialogModel
|
|||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Role}: {Content}";
|
||||
if (Role == AgentRole.Function)
|
||||
{
|
||||
return $"{Role}: {FunctionName}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"{Role}: {Content}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,10 @@ namespace BotSharp.Abstraction.MLTasks;
|
|||
|
||||
public interface IChatCompletion
|
||||
{
|
||||
// string GetChatCompletions(Agent agent, List<RoleDialogModel> conversations, Func<RoleDialogModel, Task> onMessageReceived);
|
||||
Task<bool> GetChatCompletionsAsync(Agent agent, List<RoleDialogModel> conversations, Func<RoleDialogModel, Task> onMessageReceived);
|
||||
Task<bool> GetChatCompletionsAsync(Agent agent,
|
||||
List<RoleDialogModel> conversations,
|
||||
Func<RoleDialogModel, Task> onMessageReceived,
|
||||
Func<RoleDialogModel, Task> onFunctionExecuting);
|
||||
|
||||
Task<bool> GetChatCompletionsStreamingAsync(Agent agent, List<RoleDialogModel> conversations, Func<RoleDialogModel, Task> onMessageReceived);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ public class AgentRouter : IAgentRouting
|
|||
public async Task<Agent> LoadCurrentAgent()
|
||||
{
|
||||
// Load current agent from state
|
||||
var stateService = _services.GetRequiredService<IConversationStateService>();
|
||||
var currentAgentId = stateService.GetState("agentId");
|
||||
var state = _services.GetRequiredService<IConversationStateService>();
|
||||
var currentAgentId = state.GetState("agentId");
|
||||
if (string.IsNullOrEmpty(currentAgentId))
|
||||
{
|
||||
currentAgentId = _settings.RouterId;
|
||||
|
|
@ -30,7 +30,7 @@ public class AgentRouter : IAgentRouting
|
|||
var agent = await agentService.LoadAgent(currentAgentId);
|
||||
|
||||
// Set agent and trigger state changed
|
||||
stateService.SetState("agentId", currentAgentId);
|
||||
state.SetState("agentId", currentAgentId);
|
||||
|
||||
return agent;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,14 +47,16 @@ public class ConversationController : ControllerBase, IApiAdapter
|
|||
var conv = _services.GetRequiredService<IConversationService>();
|
||||
|
||||
var response = new MessageResponseModel();
|
||||
var stackMsg = new List<RoleDialogModel>();
|
||||
|
||||
await conv.SendMessage(agentId, conversationId,
|
||||
new RoleDialogModel("user", input.Text),
|
||||
async msg =>
|
||||
response.Text = msg.Content,
|
||||
stackMsg.Add(msg),
|
||||
async fn
|
||||
=> await Task.CompletedTask);
|
||||
|
||||
response.Text = string.Join("\r\n", stackMsg.Select(x => x.Content));
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,15 +73,10 @@ public class ConversationService : IConversationService
|
|||
}
|
||||
|
||||
public async Task<bool> SendMessage(string agentId, string conversationId,
|
||||
RoleDialogModel lastDalog,
|
||||
RoleDialogModel lastDialog,
|
||||
Func<RoleDialogModel, Task> onMessageReceived,
|
||||
Func<RoleDialogModel, Task> onFunctionExecuting)
|
||||
{
|
||||
lastDalog.CurrentAgentId = agentId;
|
||||
_storage.Append(conversationId, lastDalog);
|
||||
|
||||
var wholeDialogs = GetDialogHistory(conversationId);
|
||||
|
||||
var converation = await GetConversation(conversationId);
|
||||
|
||||
// Create conversation if this conversation not exists
|
||||
|
|
@ -103,6 +98,11 @@ public class ConversationService : IConversationService
|
|||
var router = _services.GetRequiredService<IAgentRouting>();
|
||||
var agent = await router.LoadCurrentAgent();
|
||||
|
||||
lastDialog.CurrentAgentId = agent.Id;
|
||||
_storage.Append(conversationId, agent, lastDialog);
|
||||
|
||||
var wholeDialogs = GetDialogHistory(conversationId);
|
||||
|
||||
// Get relevant domain knowledge
|
||||
/*if (_settings.EnableKnowledgeBase)
|
||||
{
|
||||
|
|
@ -127,100 +127,81 @@ public class ConversationService : IConversationService
|
|||
}
|
||||
|
||||
var chatCompletion = GetChatCompletion();
|
||||
var result = await GetChatCompletionsAsyncRecursively(chatCompletion,
|
||||
conversationId,
|
||||
agent,
|
||||
wholeDialogs,
|
||||
onMessageReceived,
|
||||
onFunctionExecuting);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<bool> GetChatCompletionsAsyncRecursively(IChatCompletion chatCompletion,
|
||||
string conversationId,
|
||||
Agent agent,
|
||||
List<RoleDialogModel> wholeDialogs,
|
||||
Func<RoleDialogModel, Task> onMessageReceived,
|
||||
Func<RoleDialogModel, Task> onFunctionExecuting)
|
||||
{
|
||||
var result = await chatCompletion.GetChatCompletionsAsync(agent, wholeDialogs, async msg =>
|
||||
{
|
||||
await HandleMessage(conversationId, agent, msg, onMessageReceived, onFunctionExecuting);
|
||||
await HandleAssistantMessage(msg, onMessageReceived);
|
||||
|
||||
if (msg.NeedReloadAgent)
|
||||
// Add to dialog history
|
||||
_storage.Append(conversationId, agent, msg);
|
||||
}, async fn =>
|
||||
{
|
||||
var preAgentId = agent.Id;
|
||||
|
||||
await HandleFunctionMessage(fn, onFunctionExecuting);
|
||||
|
||||
// Agent has been transferred
|
||||
if (fn.CurrentAgentId != preAgentId)
|
||||
{
|
||||
await HandleMessageIfAgentReloaded(conversationId, agent, msg, wholeDialogs, onMessageReceived, onFunctionExecuting);
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
agent = await agentService.LoadAgent(fn.CurrentAgentId);
|
||||
|
||||
// Set state to make next conversation will go to this agent directly
|
||||
var state = _services.GetRequiredService<IConversationStateService>();
|
||||
state.SetState("agentId", fn.CurrentAgentId);
|
||||
}
|
||||
|
||||
fn.Content = fn.ExecutionResult;
|
||||
|
||||
// Add to dialog history
|
||||
_storage.Append(conversationId, agent, fn);
|
||||
|
||||
// After function is executed, pass the result to LLM to get a natural response
|
||||
wholeDialogs.Add(fn);
|
||||
|
||||
await GetChatCompletionsAsyncRecursively(chatCompletion, conversationId, agent, wholeDialogs, onMessageReceived, onFunctionExecuting);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task HandleMessage(string conversationId, Agent agent, RoleDialogModel msg,
|
||||
Func<RoleDialogModel, Task> onMessageReceived,
|
||||
Func<RoleDialogModel, Task> onFunctionExecuting)
|
||||
private async Task HandleAssistantMessage(RoleDialogModel msg, Func<RoleDialogModel, Task> onMessageReceived)
|
||||
{
|
||||
if (msg.Role == "function")
|
||||
var hooks = _services.GetServices<IConversationHook>().ToList();
|
||||
|
||||
// After chat completion hook
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
// Save states
|
||||
SaveStateByArgs(msg.FunctionArgs);
|
||||
|
||||
// Call functions
|
||||
await onFunctionExecuting(msg);
|
||||
await CallFunctions(msg);
|
||||
|
||||
// Add to dialog history
|
||||
if (msg.ExecutionResult != null)
|
||||
{
|
||||
if (msg.NeedReloadAgent)
|
||||
{
|
||||
_logger.LogInformation($"Skipped append dialog log: {msg.FunctionName}\n{msg.FunctionArgs}\n{msg.ExecutionResult}");
|
||||
return;
|
||||
}
|
||||
|
||||
_storage.Append(conversationId, new RoleDialogModel(msg.Role, msg.Content)
|
||||
{
|
||||
CurrentAgentId = agent.Id,
|
||||
FunctionName = msg.FunctionName,
|
||||
FunctionArgs = msg.FunctionArgs,
|
||||
ExecutionResult = msg.ExecutionResult
|
||||
});
|
||||
}
|
||||
await hook.AfterCompletion(msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add to dialog history
|
||||
_storage.Append(conversationId, new RoleDialogModel(msg.Role, msg.Content)
|
||||
{
|
||||
CurrentAgentId = agent.Id
|
||||
});
|
||||
|
||||
var hooks = _services.GetServices<IConversationHook>().ToList();
|
||||
// After chat completion hook
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
await hook.AfterCompletion(msg);
|
||||
}
|
||||
|
||||
await onMessageReceived(msg);
|
||||
}
|
||||
await onMessageReceived(msg);
|
||||
}
|
||||
|
||||
private async Task HandleMessageIfAgentReloaded(string conversationId, Agent agent,
|
||||
RoleDialogModel msg,
|
||||
List<RoleDialogModel> wholeDialogs,
|
||||
Func<RoleDialogModel, Task> onMessageReceived,
|
||||
Func<RoleDialogModel, Task> onFunctionExecuting)
|
||||
private async Task HandleFunctionMessage(RoleDialogModel msg, Func<RoleDialogModel, Task> onFunctionExecuting)
|
||||
{
|
||||
var state = _services.GetRequiredService<IConversationStateService>();
|
||||
var currentAgentId = state.GetState("agentId");
|
||||
// Save states
|
||||
SaveStateByArgs(msg.FunctionArgs);
|
||||
|
||||
// Send to LLM to get final response when agent is switched.
|
||||
var conv = _services.GetRequiredService<IConversationService>();
|
||||
var chatCompletion = conv.GetChatCompletion();
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var newAgent = await agentService.LoadAgent(currentAgentId);
|
||||
await chatCompletion.GetChatCompletionsAsync(newAgent, wholeDialogs, async newMsg =>
|
||||
{
|
||||
if (newMsg.Role == AgentRole.Function)
|
||||
{
|
||||
await HandleMessage(conversationId, agent, newMsg, onMessageReceived, onFunctionExecuting);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.StopPropagate = true;
|
||||
await onMessageReceived(newMsg);
|
||||
|
||||
_storage.Append(conversationId, new RoleDialogModel(newMsg.Role, newMsg.Content)
|
||||
{
|
||||
CurrentAgentId = agent.Id
|
||||
});
|
||||
}
|
||||
});
|
||||
// Call functions
|
||||
await onFunctionExecuting(msg);
|
||||
await CallFunctions(msg);
|
||||
}
|
||||
|
||||
private void SaveStateByArgs(string args)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using BotSharp.Abstraction.Agents.Enums;
|
||||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Conversations.Models;
|
||||
using System.IO;
|
||||
|
||||
|
|
@ -12,7 +13,7 @@ public class ConversationStorage : IConversationStorage
|
|||
_dbSettings = dbSettings;
|
||||
}
|
||||
|
||||
public void Append(string conversationId, RoleDialogModel dialog)
|
||||
public void Append(string conversationId, Agent agent, RoleDialogModel dialog)
|
||||
{
|
||||
var conversationFile = GetStorageFile(conversationId);
|
||||
var sb = new StringBuilder();
|
||||
|
|
@ -21,7 +22,7 @@ public class ConversationStorage : IConversationStorage
|
|||
{
|
||||
var args = dialog.FunctionArgs.Replace("\r", " ").Replace("\n", " ").Trim();
|
||||
|
||||
sb.AppendLine($"{dialog.CreatedAt}|{dialog.Role}|{dialog.CurrentAgentId}|{dialog.FunctionName}|{args}");
|
||||
sb.AppendLine($"{dialog.CreatedAt}|{dialog.Role}|{agent.Name}|{dialog.FunctionName}|{args}");
|
||||
|
||||
var content = dialog.ExecutionResult.Replace("\r", " ").Replace("\n", " ").Trim();
|
||||
if (string.IsNullOrEmpty(content))
|
||||
|
|
@ -32,7 +33,7 @@ public class ConversationStorage : IConversationStorage
|
|||
}
|
||||
else if (dialog.Role == AgentRole.Assistant)
|
||||
{
|
||||
sb.AppendLine($"{dialog.CreatedAt}|{dialog.Role}|||");
|
||||
sb.AppendLine($"{dialog.CreatedAt}|{dialog.Role}|{agent.Name}||");
|
||||
var content = dialog.Content.Replace("\r", " ").Replace("\n", " ").Trim();
|
||||
if (string.IsNullOrEmpty(content))
|
||||
{
|
||||
|
|
@ -42,7 +43,7 @@ public class ConversationStorage : IConversationStorage
|
|||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine($"{dialog.CreatedAt}|{dialog.Role}|{dialog.CurrentAgentId}||");
|
||||
sb.AppendLine($"{dialog.CreatedAt}|{dialog.Role}|{agent.Name}||");
|
||||
var content = dialog.Content.Replace("\r", " ").Replace("\n", " ").Trim();
|
||||
if (string.IsNullOrEmpty(content))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -19,7 +19,10 @@ public class ChatCompletionProvider : IChatCompletion
|
|||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task<bool> GetChatCompletionsAsync(Agent agent, List<RoleDialogModel> conversations, Func<RoleDialogModel, Task> onMessageReceived)
|
||||
public async Task<bool> GetChatCompletionsAsync(Agent agent,
|
||||
List<RoleDialogModel> conversations,
|
||||
Func<RoleDialogModel, Task> onMessageReceived,
|
||||
Func<RoleDialogModel, Task> onFunctionExecuting)
|
||||
{
|
||||
var content = string.Join("\n", conversations.Select(x => $"{x.Role}: {x.Content.Replace("user:", "User:")}")).Trim();
|
||||
content += "\nBob: ";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using Azure;
|
||||
using Azure.AI.OpenAI;
|
||||
using BotSharp.Abstraction.Agents.Enums;
|
||||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Conversations.Models;
|
||||
using BotSharp.Abstraction.Functions.Models;
|
||||
|
|
@ -31,36 +32,6 @@ public class ChatCompletionProvider : IChatCompletion
|
|||
return client;
|
||||
}
|
||||
|
||||
/*public string GetChatCompletions(Agent agent, List<RoleDialogModel> conversations, Func<RoleDialogModel, Task> onMessageReceived)
|
||||
{
|
||||
var client = GetClient();
|
||||
var chatCompletionsOptions = PrepareOptions(agent, conversations);
|
||||
|
||||
var response = client.GetChatCompletions(_settings.DeploymentModel.ChatCompletionModel, chatCompletionsOptions);
|
||||
var choice = response.Value.Choices[0];
|
||||
var message = choice.Message;
|
||||
|
||||
if (choice.FinishReason == CompletionsFinishReason.FunctionCall)
|
||||
{
|
||||
response = HandleFunctionCall(message,
|
||||
onMessageReceived,
|
||||
chatCompletionsOptions).Result;
|
||||
}
|
||||
|
||||
choice = response.Value.Choices[0];
|
||||
message = choice.Message;
|
||||
|
||||
_logger.LogInformation(message.Content);
|
||||
|
||||
if (!string.IsNullOrEmpty(message.Content))
|
||||
{
|
||||
onMessageReceived(new RoleDialogModel(ChatRole.Assistant.ToString(), message.Content))
|
||||
.Wait();
|
||||
}
|
||||
|
||||
return message.Content.Trim();
|
||||
}*/
|
||||
|
||||
public List<RoleDialogModel> GetChatSamples(string sampleText)
|
||||
{
|
||||
var samples = new List<RoleDialogModel>();
|
||||
|
|
@ -107,7 +78,10 @@ public class ChatCompletionProvider : IChatCompletion
|
|||
return functions;
|
||||
}
|
||||
|
||||
public async Task<bool> GetChatCompletionsAsync(Agent agent, List<RoleDialogModel> conversations, Func<RoleDialogModel, Task> onMessageReceived)
|
||||
public async Task<bool> GetChatCompletionsAsync(Agent agent,
|
||||
List<RoleDialogModel> conversations,
|
||||
Func<RoleDialogModel, Task> onMessageReceived,
|
||||
Func<RoleDialogModel, Task> onFunctionExecuting)
|
||||
{
|
||||
var client = GetClient();
|
||||
var chatCompletionsOptions = PrepareOptions(agent, conversations);
|
||||
|
|
@ -118,24 +92,29 @@ public class ChatCompletionProvider : IChatCompletion
|
|||
|
||||
if (choice.FinishReason == CompletionsFinishReason.FunctionCall)
|
||||
{
|
||||
response = await HandleFunctionCall(agent,
|
||||
message,
|
||||
onMessageReceived,
|
||||
chatCompletionsOptions);
|
||||
}
|
||||
_logger.LogInformation($"[{agent.Name}]: {message.FunctionCall.Name} => {message.FunctionCall.Arguments}");
|
||||
|
||||
if (response != null)
|
||||
{
|
||||
choice = response.Value.Choices[0];
|
||||
message = choice.Message;
|
||||
|
||||
_logger.LogInformation(message.Content);
|
||||
|
||||
if (!string.IsNullOrEmpty(message.Content))
|
||||
var funcContextIn = new RoleDialogModel(AgentRole.Function, message.Content)
|
||||
{
|
||||
var msgByLlm = new RoleDialogModel(ChatRole.Assistant.ToString(), message.Content);
|
||||
await onMessageReceived(msgByLlm);
|
||||
}
|
||||
CurrentAgentId = agent.Id,
|
||||
FunctionName = message.FunctionCall.Name,
|
||||
FunctionArgs = message.FunctionCall.Arguments
|
||||
};
|
||||
|
||||
// Execute functions
|
||||
await onFunctionExecuting(funcContextIn);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation($"[{agent.Name}] {message.Role}: {message.Content}");
|
||||
|
||||
var msg = new RoleDialogModel(AgentRole.Assistant, message.Content)
|
||||
{
|
||||
CurrentAgentId= agent.Id
|
||||
};
|
||||
|
||||
// Text response received
|
||||
await onMessageReceived(msg);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
@ -185,56 +164,6 @@ public class ChatCompletionProvider : IChatCompletion
|
|||
return true;
|
||||
}
|
||||
|
||||
private async Task<Response<ChatCompletions>> HandleFunctionCall(Agent agent,
|
||||
ChatMessage message,
|
||||
Func<RoleDialogModel, Task> onMessageReceived,
|
||||
ChatCompletionsOptions chatCompletionsOptions)
|
||||
{
|
||||
Response<ChatCompletions> response = default;
|
||||
|
||||
if (message.FunctionCall == null || message.FunctionCall.Arguments == null)
|
||||
{
|
||||
return response;
|
||||
}
|
||||
|
||||
_logger.LogInformation($"{message.FunctionCall.Name}: {message.FunctionCall.Arguments}");
|
||||
var funcContextIn = new RoleDialogModel(ChatRole.Function.ToString(), message.Content)
|
||||
{
|
||||
CurrentAgentId = agent.Id,
|
||||
FunctionName = message.FunctionCall.Name,
|
||||
FunctionArgs = message.FunctionCall.Arguments
|
||||
};
|
||||
|
||||
// Execute functions
|
||||
await onMessageReceived(funcContextIn);
|
||||
|
||||
if (funcContextIn.StopPropagate)
|
||||
{
|
||||
return response;
|
||||
}
|
||||
|
||||
if (funcContextIn.IsConversationEnd)
|
||||
{
|
||||
await onMessageReceived(new RoleDialogModel(ChatRole.Assistant.ToString(), funcContextIn.Content)
|
||||
{
|
||||
IsConversationEnd = true
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
// After function is executed, pass the result to LLM
|
||||
if (funcContextIn.ExecutionResult != null)
|
||||
{
|
||||
chatCompletionsOptions.Messages.Add(new ChatMessage(ChatRole.Function, funcContextIn.ExecutionResult)
|
||||
{
|
||||
Name = funcContextIn.FunctionName
|
||||
});
|
||||
var client = GetClient();
|
||||
response = client.GetChatCompletions(_settings.DeploymentModel.ChatCompletionModel, chatCompletionsOptions);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private ChatCompletionsOptions PrepareOptions(Agent agent, List<RoleDialogModel> conversations)
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue