Merge pull request #655 from Qtoss-AI/master

improvement
This commit is contained in:
Haiping 2024-09-23 14:33:54 -05:00 committed by GitHub
commit 0e2a457d86
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 369 additions and 77 deletions

View file

@ -5,7 +5,7 @@ public interface IKnowledgeHook
Task<List<KnowledgeChunk>> CollectChunkedKnowledge()
=> Task.FromResult(new List<KnowledgeChunk>());
Task<List<string>> GetRelevantKnowledges()
Task<List<string>> GetRelevantKnowledges(string text)
=> Task.FromResult(new List<string>());
Task<List<string>> GetGlobalKnowledges()

View file

@ -15,6 +15,7 @@ public interface IKnowledgeService
Task<bool> DeleteVectorCollectionAllData(string collectionName);
Task<bool> CreateVectorCollectionData(string collectionName, VectorCreateModel create);
Task<bool> UpdateVectorCollectionData(string collectionName, VectorUpdateModel update);
Task<bool> UpsertVectorCollectionData(string collectionName, VectorUpdateModel update);
#endregion
#region Graph

View file

@ -5,6 +5,9 @@ public class GenericTemplateMessage<T> : IRichMessage, ITemplateMessage
[JsonPropertyName("rich_type")]
public string RichType => RichTypeEnum.GenericTemplate;
/// <summary>
/// Use model refined content if leaving blank
/// </summary>
[JsonPropertyName("text")]
[Translate]
public string Text { get; set; } = string.Empty;

View file

@ -4,6 +4,7 @@ public interface IPlanningHook
{
Task<string> GetSummaryAdditionalRequirements(string planner)
=> Task.FromResult(string.Empty);
Task OnPlanningCompleted(string planner, RoleDialogModel msg)
=> Task.CompletedTask;
}

View file

@ -21,7 +21,10 @@ public interface IBotSharpRepository
#region User
User? GetUserByEmail(string email) => throw new NotImplementedException();
User? GetUserByPhone(string phone) => throw new NotImplementedException();
User? GetUserById(string id) => throw new NotImplementedException();
User? GetAffiliateUserByPhone(string phone) => throw new NotImplementedException();
User? GetUserById(string id) => throw new NotImplementedException();
List<User> GetUserByIds(List<string> ids) => throw new NotImplementedException();
User? GetUserByAffiliateId(string affiliateId) => throw new NotImplementedException();
User? GetUserByUserName(string userName) => throw new NotImplementedException();
void CreateUser(User user) => throw new NotImplementedException();
void UpdateUserVerified(string userId) => throw new NotImplementedException();
@ -29,6 +32,7 @@ public interface IBotSharpRepository
void UpdateUserPassword(string userId, string password) => throw new NotImplementedException();
void UpdateUserEmail(string userId, string email)=> throw new NotImplementedException();
void UpdateUserPhone(string userId, string Iphone) => throw new NotImplementedException();
void UpdateUserIsDisable(string userId, bool isDisable) => throw new NotImplementedException();
#endregion
#region Agent

View file

@ -13,9 +13,9 @@ public class UserRole
public const string CSR = "csr";
/// <summary>
/// Client
/// Authorized user
/// </summary>
public const string Client = "client";
public const string User = "user";
/// <summary>
/// Back office operations

View file

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BotSharp.Abstraction.Users.Enums
{
public static class UserSource
{
public const string Internal = "internal";
}
}

View file

@ -0,0 +1,8 @@
namespace BotSharp.Abstraction.Users.Enums;
public class UserType
{
public const string Internal = "internal";
public const string Client = "client";
public const string Affiliate = "affiliate";
}

View file

@ -8,6 +8,7 @@ public interface IUserService
Task<User> GetUser(string id);
Task<User> CreateUser(User user);
Task<Token> ActiveUser(UserActivationModel model);
Task<Token?> GetAffiliateToken(string authorization);
Task<Token?> GetToken(string authorization);
Task<User> GetMyProfile();
Task<bool> VerifyUserNameExisting(string userName);
@ -16,4 +17,6 @@ public interface IUserService
Task<bool> ResetUserPassword(User user);
Task<bool> ModifyUserEmail(string email);
Task<bool> ModifyUserPhone(string phone);
Task<bool> UpdatePassword(string newPassword, string verificationCode);
Task<DateTime> GetUserTokenExpires();
}

View file

@ -12,11 +12,17 @@ public class User
public string? Phone { get; set; }
public string Salt { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public string Source { get; set; } = "internal";
public string Source { get; set; } = UserSource.Internal;
public string? ExternalId { get; set; }
public string Role { get; set; } = UserRole.Client;
/// <summary>
/// internal, client, affiliate
/// </summary>
public string Type { get; set; } = UserType.Client;
public string Role { get; set; } = UserRole.User;
public string? VerificationCode { get; set; }
public bool Verified { get; set; }
public string? AffiliateId { get; set; }
public bool IsDisabled { get; set; }
public DateTime UpdatedTime { get; set; } = DateTime.UtcNow;
public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
}

View file

@ -6,4 +6,5 @@ public class AccountSetting
/// Whether to enable verification code to verify the authenticity of new users
/// </summary>
public bool NewUserVerification { get; set; }
public string[] AllowMultipleDeviceLoginUserIds { get; set; } = [];
}

View file

@ -5,5 +5,7 @@ public class VectorCollectionData
public string Id { get; set; }
public Dictionary<string, string> Data { get; set; } = new();
public double? Score { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public float[]? Vector { get; set; }
}

View file

@ -1,5 +1,3 @@
using BotSharp.Abstraction.Knowledges.Enums;
namespace BotSharp.Abstraction.VectorStorage.Models;
public class VectorSearchResult : VectorCollectionData

View file

@ -123,6 +123,12 @@ public partial class ConversationService
Message = new TextMessage(response.SecondaryContent ?? response.Content)
};
// Use model refined response
if (string.IsNullOrEmpty(response.RichContent.Message.Text))
{
response.RichContent.Message.Text = response.Content;
}
// Patch return function name
if (response.PostbackFunctionName != null)
{

View file

@ -126,7 +126,7 @@ public class ConversationStateService : IConversationStateService, IDisposable
_historyStates = _db.GetConversationStates(conversationId);
var dialogs = _db.GetConversationDialogs(conversationId);
var userDialogs = dialogs.Where(x => x.MetaData?.Role == AgentRole.User || x.MetaData?.Role == UserRole.Client)
var userDialogs = dialogs.Where(x => x.MetaData?.Role == AgentRole.User || x.MetaData?.Role == UserRole.User)
.GroupBy(x => x.MetaData?.MessageId)
.Select(g => g.First())
.OrderBy(x => x.MetaData?.CreateTime)

View file

@ -16,11 +16,26 @@ public partial class FileRepository
return Users.FirstOrDefault(x => x.Phone == phone);
}
public User? GetAffiliateUserByPhone(string phone)
{
return Users.FirstOrDefault(x => x.Phone == phone && x.Type == UserType.Affiliate);
}
public User? GetUserById(string id = null)
{
return Users.FirstOrDefault(x => x.Id == id || (x.ExternalId != null && x.ExternalId == id));
}
public List<User> GetUserByIds(List<string> ids)
{
return Users.Where(x => ids.Contains(x.Id) || (x.ExternalId != null && ids.Contains(x.ExternalId)))?.ToList() ?? new List<User>();
}
public User? GetUserByAffiliateId(string affiliateId)
{
return Users.FirstOrDefault(x => x.AffiliateId == affiliateId);
}
public User? GetUserByUserName(string userName = null)
{
return Users.FirstOrDefault(x => x.UserName == userName.ToLower());

View file

@ -1,3 +1,5 @@
using BotSharp.Abstraction.Users.Enums;
using BotSharp.Abstraction.Infrastructures;
using BotSharp.Abstraction.Users.Models;
using BotSharp.Abstraction.Users.Settings;
using BotSharp.OpenAPI.ViewModels.Users;
@ -7,6 +9,7 @@ using NanoidDotNet;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text.RegularExpressions;
using System.Net;
namespace BotSharp.Core.Users.Services;
@ -33,7 +36,7 @@ public class UserService : IUserService
if (string.IsNullOrEmpty(user.UserName))
{
// generate unique name
var name = user.Email.Split("@").First() + "-" + Nanoid.Generate("0123456789botsharp", 6);
var name = Nanoid.Generate("0123456789botsharp", 10);
user.UserName = name;
}
else
@ -56,7 +59,7 @@ public class UserService : IUserService
record = user;
record.Email = user.Email?.ToLower();
if (user.Phone != null)
if (!string.IsNullOrWhiteSpace(user.Phone))
{
record.Phone = "+" + Regex.Match(user.Phone, @"\d+").Value;
}
@ -83,12 +86,62 @@ public class UserService : IUserService
return record;
}
public async Task<bool> UpdatePassword(string password, string verificationCode)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var record = db.GetUserByUserName(_user.UserName);
if (record == null)
{
return false;
}
if (record.VerificationCode != verificationCode)
{
return false;
}
var newPassword = Utilities.HashTextMd5($"{password}{record.Salt}");
db.UpdateUserPassword(record.Id, newPassword);
return true;
}
public async Task<Token> GetAffiliateToken(string authorization)
{
var base64 = Encoding.UTF8.GetString(Convert.FromBase64String(authorization));
var (id, password) = base64.SplitAsTuple(":");
var db = _services.GetRequiredService<IBotSharpRepository>();
var record = db.GetUserByPhone(id);
var isCanLoginAffiliateRoleType = record != null && !record.IsDisabled && record.Type != UserType.Client;
if (!isCanLoginAffiliateRoleType)
{
return default;
}
if (Utilities.HashTextMd5($"{password}{record.Salt}") != record.Password)
{
return default;
}
var accessToken = GenerateJwtToken(record);
var jwt = new JwtSecurityTokenHandler().ReadJwtToken(accessToken);
var token = new Token
{
AccessToken = accessToken,
ExpireTime = jwt.Payload.Exp.Value,
TokenType = "Bearer",
Scope = "api"
};
return token;
}
public async Task<Token?> GetToken(string authorization)
{
var base64 = Encoding.UTF8.GetString(Convert.FromBase64String(authorization));
var (id, password) = base64.SplitAsTuple(":");
var hooks = _services.GetServices<IAuthenticationHook>();
var db = _services.GetRequiredService<IBotSharpRepository>();
var record = id.Contains("@") ? db.GetUserByEmail(id) : db.GetUserByUserName(id);
if (record == null)
@ -96,6 +149,12 @@ public class UserService : IUserService
record = db.GetUserByUserName(id);
}
if (record != null && record.Type == UserType.Affiliate)
{
return default;
}
var hooks = _services.GetServices<IAuthenticationHook>();
//verify password is correct or not.
if (record != null && !hooks.Any())
{
@ -108,7 +167,7 @@ public class UserService : IUserService
User? user = record;
var isAuthenticatedByHook = false;
if (record == null || record.Source != "internal")
if (record == null || record.Source != UserSource.Internal)
{
// check 3rd party user
foreach (var hook in hooks)
@ -119,7 +178,7 @@ public class UserService : IUserService
continue;
}
if (string.IsNullOrEmpty(user.Source) || user.Source == "internal")
if (string.IsNullOrEmpty(user.Source) || user.Source == UserSource.Internal)
{
_logger.LogError($"Please set source name in the Authenticate hook.");
return null;
@ -137,6 +196,8 @@ public class UserService : IUserService
Source = user.Source,
ExternalId = user.ExternalId,
Password = user.Password,
Type = user.Type,
Role = user.Role
};
await CreateUser(record);
}
@ -190,6 +251,8 @@ public class UserService : IUserService
new Claim(JwtRegisteredClaimNames.FamilyName, user?.LastName ?? string.Empty),
new Claim("source", user.Source),
new Claim("external_id", user.ExternalId ?? string.Empty),
new Claim("type", user.Type ?? UserType.Client),
new Claim("role", user.Role ?? UserRole.User),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim("phone", user.Phone ?? string.Empty)
};
@ -205,10 +268,11 @@ public class UserService : IUserService
var audience = config["Jwt:Audience"];
var expireInMinutes = int.Parse(config["Jwt:ExpireInMinutes"] ?? "120");
var key = Encoding.ASCII.GetBytes(config["Jwt:Key"]);
var expires = DateTime.UtcNow.AddMinutes(expireInMinutes);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.UtcNow.AddMinutes(expireInMinutes),
Expires = expires,
Issuer = issuer,
Audience = audience,
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key),
@ -216,9 +280,27 @@ public class UserService : IUserService
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
SaveUserTokenExpiresCache(user.Id, expires).GetAwaiter().GetResult();
return tokenHandler.WriteToken(token);
}
private async Task SaveUserTokenExpiresCache(string userId, DateTime expires)
{
var _cacheService = _services.GetRequiredService<ICacheService>();
await _cacheService.SetAsync<DateTime>(GetUserTokenExpiresCacheKey(userId), expires, null);
}
private string GetUserTokenExpiresCacheKey(string userId)
{
return $"user_{userId}_token_expires";
}
public async Task<DateTime> GetUserTokenExpires()
{
var _cacheService = _services.GetRequiredService<ICacheService>();
return await _cacheService.GetAsync<DateTime>(GetUserTokenExpiresCacheKey(_user.Id));
}
[MemoryCache(10 * 60, perInstanceCache: true)]
public async Task<User> GetMyProfile()
{
@ -323,24 +405,32 @@ public class UserService : IUserService
public async Task<bool> SendVerificationCodeResetPassword(User user)
{
if (!string.IsNullOrEmpty(user.Email) && !string.IsNullOrEmpty(user.Phone))
{
return false;
}
var db = _services.GetRequiredService<IBotSharpRepository>();
User? record = null;
if (!string.IsNullOrEmpty(user.Email))
if (!string.IsNullOrWhiteSpace(_user.Id))
{
record = db.GetUserByEmail(user.Email);
record = db.GetUserById(_user.Id);
}
else
{
if (!string.IsNullOrEmpty(user.Email) && !string.IsNullOrEmpty(user.Phone))
{
return false;
}
if (!string.IsNullOrEmpty(user.Email))
{
record = db.GetUserByEmail(user.Email);
}
if (!string.IsNullOrEmpty(user.Phone))
{
record = db.GetUserByPhone(user.Phone);
}
}
if (!string.IsNullOrEmpty(user.Phone))
{
record = db.GetUserByPhone(user.Phone);
}
if (record == null)
{
return false;

View file

@ -10,6 +10,12 @@
<OutputPath>$(SolutionDir)packages</OutputPath>
</PropertyGroup>
<ItemGroup>
<Compile Remove="packages\**" />
<EmbeddedResource Remove="packages\**" />
<None Remove="packages\**" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\..\arts\Icon.png">
<Pack>True</Pack>

View file

@ -11,6 +11,7 @@ using Microsoft.Net.Http.Headers;
using Microsoft.OpenApi.Models;
using Microsoft.IdentityModel.JsonWebTokens;
using BotSharp.OpenAPI.BackgroundServices;
using BotSharp.OpenAPI.Filters;
namespace BotSharp.OpenAPI;
@ -32,6 +33,15 @@ public static class BotSharpOpenApiExtensions
services.AddScoped<IUserIdentity, UserIdentity>();
services.AddHostedService<ConversationTimeoutService>();
var enableSingleLogin = bool.Parse(config["Jwt:EnableSingleLogin"] ?? "false");
if (enableSingleLogin)
{
services.AddMvc(options =>
{
options.Filters.Add<UserSingleLoginFilter>();
});
}
// Add bearer authentication
var schema = "MIXED_SCHEME";
var builder = services.AddAuthentication(options =>

View file

@ -121,6 +121,16 @@ public class UserController : ControllerBase
return await _userService.ResetUserPassword(user.ToUser());
}
[HttpPost("/user/updatepassword")]
public async Task<bool> UpdatePassword([FromBody] User user)
{
if (string.IsNullOrWhiteSpace(user.Password) || string.IsNullOrWhiteSpace(user.VerificationCode))
{
return false;
}
return await _userService.UpdatePassword(user.Password, user.VerificationCode);
}
[HttpPost("/user/email/modify")]
public async Task<bool> ModifyUserEmail([FromQuery] string email)
{

View file

@ -0,0 +1,71 @@
using BotSharp.Abstraction.Users.Settings;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Configuration;
using Microsoft.Net.Http.Headers;
using System.IdentityModel.Tokens.Jwt;
namespace BotSharp.OpenAPI.Filters
{
public class UserSingleLoginFilter : IAuthorizationFilter
{
private readonly IUserService _userService;
private readonly IServiceProvider _services;
public UserSingleLoginFilter(IUserService userService, IServiceProvider services)
{
_userService = userService;
_services = services;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var bearerToken = GetBearerToken(context);
if (!string.IsNullOrWhiteSpace(bearerToken))
{
var config = _services.GetRequiredService<AccountSetting>();
var token = GetJwtToken(bearerToken);
if (config.AllowMultipleDeviceLoginUserIds.Contains(token.Claims.First(x => x.Type == "nameid").Value))
{
return;
}
var validTo = token.ValidTo.ToLongTimeString();
var currentExpires = GetUserExpires().ToLongTimeString();
if (validTo != currentExpires)
{
Serilog.Log.Warning($"Token expired. Token expires at {validTo}, current expires at {currentExpires}");
context.Result = new UnauthorizedResult();
}
}
}
private string GetBearerToken(AuthorizationFilterContext context)
{
if (context.HttpContext.Request.Headers.TryGetValue(HeaderNames.Authorization, out var bearerToken)
&& !string.IsNullOrWhiteSpace(bearerToken.ToString()))
{
var tokenType = bearerToken.ToString().Split(" ").First();
if (tokenType == JwtBearerDefaults.AuthenticationScheme)
{
return bearerToken.ToString().Split(" ").Last();
}
}
return null;
}
private JwtSecurityToken GetJwtToken(string jwtToken)
{
var handler = new JwtSecurityTokenHandler();
var token = handler.ReadJwtToken(jwtToken);
return token;
}
private DateTime GetUserExpires()
{
return _userService.GetUserTokenExpires().GetAwaiter().GetResult();
}
}
}

View file

@ -10,7 +10,8 @@ public class UserCreationModel
public string? Email { get; set; }
public string? Phone { get; set; }
public string Password { get; set; } = string.Empty;
public string Role { get; set; } = UserRole.Client;
public string Type { get; set; } = UserType.Client;
public string Role { get; set; } = UserRole.User;
public User ToUser()
{
@ -22,7 +23,8 @@ public class UserCreationModel
Email = Email,
Phone = Phone,
Password = Password,
Role = Role
Role = Role,
Type = Type
};
}
}

View file

@ -14,7 +14,8 @@ public class UserViewModel
public string? LastName { get; set; }
public string? Email { get; set; }
public string? Phone { get; set; }
public string Role { get; set; } = UserRole.Client;
public string Type { get; set; } = UserType.Client;
public string Role { get; set; } = UserRole.User;
[JsonPropertyName("full_name")]
public string FullName => $"{FirstName} {LastName}".Trim();
public string Source { get; set; }
@ -34,6 +35,7 @@ public class UserViewModel
{
FirstName = "Unknown",
LastName = "Anonymous",
Type = UserType.Client,
Role = AgentRole.User
};
}
@ -46,6 +48,7 @@ public class UserViewModel
LastName = user.LastName,
Email = user.Email,
Phone = user.Phone,
Type = user.Type,
Role = user.Role,
Source = user.Source,
ExternalId = user.ExternalId,

View file

@ -164,6 +164,42 @@ public partial class KnowledgeService
}
}
public async Task<bool> UpsertVectorCollectionData(string collectionName, VectorUpdateModel update)
{
try
{
if (string.IsNullOrWhiteSpace(collectionName) || string.IsNullOrWhiteSpace(update.Text) || !Guid.TryParse(update.Id, out var guid))
{
return false;
}
var db = GetVectorDb();
var found = await db.GetCollectionData(collectionName, new List<Guid> { guid },
withVector: true,
withPayload: true);
if (!found.IsNullOrEmpty())
{
if (found.First().Data["text"] == update.Text)
{
// Only update payload
return await db.Upsert(collectionName, guid, found.First().Vector, update.Text, update.Payload);
}
}
var textEmbedding = GetTextEmbedding(collectionName);
var vector = await textEmbedding.GetVectorAsync(update.Text);
var payload = update.Payload ?? new();
payload[KnowledgePayloadName.DataSource] = !string.IsNullOrWhiteSpace(update.DataSource) ? update.DataSource : VectorDataSource.Api;
return await db.Upsert(collectionName, guid, vector, update.Text, payload);
}
catch (Exception ex)
{
_logger.LogWarning($"Error when updating vector collection data. {ex.Message}\r\n{ex.InnerException}");
return false;
}
}
public async Task<bool> DeleteVectorCollectionData(string collectionName, string id)
{
try

View file

@ -1,3 +1,4 @@
using BotSharp.Abstraction.Users.Enums;
using BotSharp.Abstraction.Users.Models;
namespace BotSharp.Plugin.MongoStorage.Collections;
@ -11,11 +12,14 @@ public class UserDocument : MongoBase
public string? Phone { get; set; }
public string Salt { get; set; } = null!;
public string Password { get; set; } = null!;
public string Source { get; set; } = "internal";
public string Source { get; set; } = UserSource.Internal;
public string? ExternalId { get; set; }
public string Type { get; set; } = UserType.Client;
public string Role { get; set; } = null!;
public string? VerificationCode { get; set; }
public bool Verified { get; set; }
public string? AffiliateId { get; set; }
public bool IsDisabled { get; set; }
public DateTime CreatedTime { get; set; }
public DateTime UpdatedTime { get; set; }
@ -33,7 +37,10 @@ public class UserDocument : MongoBase
Salt = Salt,
Source = Source,
ExternalId = ExternalId,
Type = Type,
Role = Role,
AffiliateId = AffiliateId,
IsDisabled = IsDisabled,
VerificationCode = VerificationCode,
Verified = Verified,
};

View file

@ -1,3 +1,4 @@
using BotSharp.Abstraction.Users.Enums;
using BotSharp.Abstraction.Users.Models;
namespace BotSharp.Plugin.MongoStorage.Repository;
@ -16,6 +17,12 @@ public partial class MongoRepository
return user != null ? user.ToUser() : null;
}
public User? GetAffiliateUserByPhone(string phone)
{
var user = _dc.Users.AsQueryable().FirstOrDefault(x => x.Phone == phone && x.Type == UserType.Affiliate);
return user != null ? user.ToUser() : null;
}
public User? GetUserById(string id)
{
var user = _dc.Users.AsQueryable()
@ -23,6 +30,20 @@ public partial class MongoRepository
return user != null ? user.ToUser() : null;
}
public List<User> GetUserByIds(List<string> ids)
{
var users = _dc.Users.AsQueryable()
.Where(x => ids.Contains(x.Id) || (x.ExternalId != null && ids.Contains(x.ExternalId))).ToList();
return users?.Any() == true ? users.Select(x => x.ToUser()).ToList() : new List<User>();
}
public User? GetUserByAffiliateId(string affiliateId)
{
var user = _dc.Users.AsQueryable()
.FirstOrDefault(x => x.AffiliateId == affiliateId);
return user != null ? user.ToUser() : null;
}
public User? GetUserByUserName(string userName)
{
var user = _dc.Users.AsQueryable().FirstOrDefault(x => x.UserName == userName.ToLower());
@ -46,8 +67,11 @@ public partial class MongoRepository
Source = user.Source,
ExternalId = user.ExternalId,
Role = user.Role,
Type = user.Type,
VerificationCode = user.VerificationCode,
Verified = user.Verified,
AffiliateId = user.AffiliateId,
IsDisabled = user.IsDisabled,
CreatedTime = DateTime.UtcNow,
UpdatedTime = DateTime.UtcNow
};
@ -94,4 +118,12 @@ public partial class MongoRepository
.Set(x => x.UpdatedTime, DateTime.UtcNow);
_dc.Users.UpdateOne(filter, update);
}
public void UpdateUserIsDisable(string userId, bool isDisable)
{
var filter = Builders<UserDocument>.Filter.Eq(x => x.Id, userId);
var update = Builders<UserDocument>.Update.Set(x => x.IsDisabled, isDisable)
.Set(x => x.UpdatedTime, DateTime.UtcNow);
_dc.Users.UpdateOne(filter, update);
}
}

View file

@ -27,6 +27,7 @@ public class PrimaryStagePlanFn : IFunctionCallback
var collectionName = knowledgeSettings.Default.CollectionName ?? KnowledgeCollectionName.BotSharp;
// Get knowledge from vectordb
var hooks = _services.GetServices<IKnowledgeHook>();
var knowledges = new List<string>();
foreach (var question in task.Questions)
{
@ -34,8 +35,13 @@ public class PrimaryStagePlanFn : IFunctionCallback
{
Confidence = 0.2f
});
knowledges.Add(string.Join("\r\n\r\n=====\r\n", list.Select(x => x.ToQuestionAnswer())));
foreach (var hook in hooks)
{
var k = await hook.GetRelevantKnowledges(question);
knowledges.AddRange(k);
}
}
// Get first stage planning prompt
@ -92,7 +98,7 @@ public class PrimaryStagePlanFn : IFunctionCallback
var wholeDialogs = conv.GetDialogHistory();
// Append text
wholeDialogs.Last().Content += "\n\nYou must analyze the table description to infer the table relations.";
wholeDialogs.Last().Content += "\n\nYou must analyze the table description to infer the table relations. Only output the JSON result.";
var completion = CompletionProvider.GetChatCompletion(_services,
provider: plannerAgent.LlmConfig.Provider,

View file

@ -75,7 +75,6 @@ public class SecondaryStagePlanFn : IFunctionCallback
var template = agent.Templates.FirstOrDefault(x => x.Name == "two_stage.2nd.plan")?.Content ?? string.Empty;
var responseFormat = JsonSerializer.Serialize(new SecondStagePlan
{
Tool = "tool name if task solution provided",
Parameters = [ JsonDocument.Parse("{}") ],
Results = [ string.Empty ]
});

View file

@ -8,9 +8,6 @@ public class SecondStagePlan
[JsonPropertyName("description")]
public string Description { get; set; } = "";
[JsonPropertyName("tool_name")]
public string Tool { get; set; } = "";
[JsonPropertyName("input_args")]
public JsonDocument[] Parameters { get; set; } = [];

View file

@ -96,31 +96,6 @@ public partial class TwoStageTaskPlanner : IRoutingPlaner
return true;
}
private async Task<string> GetFirstStagePlanPrompt(Agent router)
{
var template = router.Templates.First(x => x.Name == "two_stage.1st.plan").Content;
var responseFormat = JsonSerializer.Serialize(new FirstStagePlan
{
Parameters = new JsonDocument[] { JsonDocument.Parse("{}") },
Results = new string[] { "" }
});
var relevantKnowledges = new List<string>();
var hooks = _services.GetServices<IKnowledgeHook>();
foreach (var hook in hooks)
{
var k = await hook.GetRelevantKnowledges();
relevantKnowledges.AddRange(k);
}
var render = _services.GetRequiredService<ITemplateRender>();
return render.Render(template, new Dictionary<string, object>
{
{ "response_format", responseFormat },
{ "relevant_knowledges", relevantKnowledges.ToArray() }
});
}
private async Task<string> GetNextStepPrompt(Agent router)
{
var agentService = _services.GetRequiredService<IAgentService>();
@ -134,17 +109,4 @@ public partial class TwoStageTaskPlanner : IRoutingPlaner
{ StateConst.EXPECTED_GOAL_AGENT, states.GetState(StateConst.EXPECTED_GOAL_AGENT) }
});
}
private string GetSecondStageTaskPrompt(Agent router, SecondStagePlan plan)
{
var template = router.Templates.First(x => x.Name == "planner_prompt.two_stage.2nd.task").Content;
var render = _services.GetRequiredService<ITemplateRender>();
return render.Render(template, new Dictionary<string, object>
{
{ "task_description", plan.Description },
{ "related_tables", plan.Tables },
{ "input_arguments", JsonSerializer.Serialize(plan.Parameters) },
{ "output_results", JsonSerializer.Serialize(plan.Results) },
});
}
}

View file

@ -1,7 +1,8 @@
Use the TwoStagePlanner approach to plan the overall implementation steps, follow the below steps strictly.
1. call plan_primary_stage to generate the primary plan.
1. Call plan_primary_stage to generate the primary plan.
2. If need_additional_information is true, call plan_secondary_stage for the specific primary stage.
3. You must call plan_summary for you final planned output.
3. You must call plan_summary to generate final planned steps.
4. If you can't generate the final accurate planning steps due to missing some specific informations, please ask user for more information.
*** IMPORTANT ***
Don't run the planning process repeatedly if you have already got the result of user's request.

View file

@ -10,4 +10,3 @@ Expected user goal agent is {{ expected_user_goal_agent }}.
{%- else -%}
User goal agent is inferred based on user initial request.
{%- endif %}
Always route to planner first.