1057 lines
33 KiB
C#
1057 lines
33 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using AutoMapper;
|
|
using Microsoft.Extensions.AI;
|
|
using OllamaSharp;
|
|
using xAiApi.AI.Configuration;
|
|
using xAiApi.AI.Constants;
|
|
using xAiApi.AI.Interfaces;
|
|
using xAiApi.AI.Interfaces.Entities;
|
|
using xAiApi.AI.Models.Dtos;
|
|
using xAiApi.AI.Models.Entities;
|
|
using xAiModels.Constants;
|
|
using xAiModels.Extensions;
|
|
using xAiModels.Models;
|
|
using xCommons.Extensions;
|
|
using xExceptions.Constants;
|
|
using xIdentityModels.Models;
|
|
using xIdentityService.Extensions;
|
|
using xIdentityService.Interfaces;
|
|
using xModels.Dtos;
|
|
|
|
namespace xAiApi.AI.Services
|
|
{
|
|
/// <summary>
|
|
/// Provide Ai Capabilities Using AI Service ...
|
|
/// using User Data Info ...
|
|
/// </summary>
|
|
public class XAiProvider : IXAiProvider
|
|
{
|
|
//
|
|
#region Properties ...
|
|
/// <summary>
|
|
/// Configuration ...
|
|
/// </summary>
|
|
/// <value></value>
|
|
public XAiApiConfiguration Configuration { get; }
|
|
|
|
/// <summary>
|
|
/// Mapper Service ...
|
|
/// </summary>
|
|
private readonly IMapper mapper;
|
|
|
|
/// <summary>
|
|
/// LLm Chat Client ...
|
|
/// used for Interacting by LLM ...
|
|
/// </summary>
|
|
private readonly IChatClient chatClient;
|
|
|
|
/// <summary>
|
|
/// LLM Configuration Options ...
|
|
/// usually used for Enable Function Tools and etc ...
|
|
/// </summary>
|
|
private readonly ChatOptions chatOptions;
|
|
|
|
/// <summary>
|
|
/// Identity Provider Service ...
|
|
/// use for Validate User Info ...
|
|
/// </summary>
|
|
private readonly IXIdentityProvider identityProvider;
|
|
|
|
/// <summary>
|
|
/// Ai Project Repository Service ...
|
|
/// <see cref="IXAiProjectRepository"/>
|
|
/// </summary>
|
|
private readonly IXAiProjectRepository aiProjectRepository;
|
|
|
|
/// <summary>
|
|
/// Ai Message Repository Service ...
|
|
/// <see cref="IXAiMessageRepository"/>
|
|
/// </summary>
|
|
private readonly IXAiMessageRepository aiMessageRepository;
|
|
|
|
/// <summary>
|
|
/// Ai Conversation Repository Service ...
|
|
/// <see cref="IXAiConversationRepository"/>
|
|
/// </summary>
|
|
private readonly IXAiConversationRepository aiConversationRepository;
|
|
#endregion
|
|
|
|
//
|
|
#region Constructor ...
|
|
public XAiProvider(
|
|
IMapper mapper,
|
|
XAiApiConfiguration configuration,
|
|
IXIdentityProvider identityProvider,
|
|
IXAiProjectRepository aiProjectRepository,
|
|
IXAiMessageRepository aiMessageRepository,
|
|
IXAiConversationRepository aiConversationRepository
|
|
)
|
|
{
|
|
//
|
|
Configuration = configuration;
|
|
|
|
//
|
|
this.mapper = mapper;
|
|
this.identityProvider = identityProvider;
|
|
this.aiProjectRepository = aiProjectRepository;
|
|
this.aiMessageRepository = aiMessageRepository;
|
|
this.aiConversationRepository = aiConversationRepository;
|
|
|
|
//
|
|
// Prepare Chat Model ...
|
|
chatClient = new ChatClientBuilder(new OllamaApiClient(
|
|
new Uri(configuration.Url),
|
|
configuration.Model
|
|
))
|
|
// .UseFunctionInvocation()
|
|
.Build();
|
|
|
|
//
|
|
// Configuring Chant Options ...
|
|
chatOptions = new ChatOptions()
|
|
{
|
|
//
|
|
// Preparing Tools ...
|
|
// Tools = [
|
|
// //
|
|
// // Tempreature Tool ...
|
|
// AIFunctionFactory.Create((string location, string unit) => {
|
|
// //
|
|
// var temp = Random.Shared.Next(5, 20);
|
|
// var cond = Random.Shared.Next(0, 1) == 0 ? "sunny" : "rainy";
|
|
|
|
// //
|
|
// var result = $"The weather is {temp} degrees C and {cond}.";
|
|
|
|
// //
|
|
// return result;
|
|
// },
|
|
// "get_current_weather",
|
|
// "Get the current weather in given location"
|
|
// )
|
|
// ],
|
|
};
|
|
}
|
|
#endregion
|
|
|
|
//
|
|
#region Helpers ...
|
|
/// <summary>
|
|
/// Converts Entity to Dto ...
|
|
/// <see cref="XAiProjectDto"/>
|
|
/// </summary>
|
|
/// <param name="entity"><see cref="XAiProject"/></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<XAiProjectDto> ToDto(
|
|
XAiProject entity,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
var result = mapper.Map<XAiProjectDto>(entity);
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts Entity to Dto ...
|
|
/// <see cref="XAiMessageDto"/>
|
|
/// </summary>
|
|
/// <param name="entity"><see cref="XAiMessage"/></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<XAiMessageDto> ToDto(
|
|
XAiMessage entity,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
var result = mapper.Map<XAiMessageDto>(entity);
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts Entity to Dto ...
|
|
/// <see cref="XAiConversationDto"/>
|
|
/// </summary>
|
|
/// <param name="entity"><see cref="XAiConversation"/></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<XAiConversationDto> ToDto(
|
|
XAiConversation entity,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
var result = mapper.Map<XAiConversationDto>(entity);
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts a List of Entities to Dtos ...
|
|
/// <see cref="XAiProjectDto"/>
|
|
/// </summary>
|
|
/// <param name="entities"><see cref="XAiProject"/></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<IEnumerable<XAiProjectDto>> ToDtos(
|
|
IEnumerable<XAiProject> entities,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
var result = mapper.Map<IEnumerable<XAiProjectDto>>(entities);
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts a List of Entities to Dtos ...
|
|
/// <see cref="XAiMessageDto"/>
|
|
/// </summary>
|
|
/// <param name="entities"><see cref="XAiMessage"/></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<IEnumerable<XAiMessageDto>> ToDtos(
|
|
IEnumerable<XAiMessage> entities,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
var result = mapper.Map<IEnumerable<XAiMessageDto>>(entities);
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts a List of Entities to Dtos ...
|
|
/// <see cref="XAiConversationDto"/>
|
|
/// </summary>
|
|
/// <param name="entities"><see cref="XAiConversation"/></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<IEnumerable<XAiConversationDto>> ToDtos(
|
|
IEnumerable<XAiConversation> entities,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
var result = mapper.Map<IEnumerable<XAiConversationDto>>(entities);
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts a QueryResult of Entities to Dtos ...
|
|
/// <see cref="XAiProjectDto"/>
|
|
/// </summary>
|
|
/// <param name="queryResult"><see cref="XAiProject"/></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<XQueryResult<XAiProjectDto>> ToDtoQueryResult(
|
|
XQueryResult<XAiProject> queryResult,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
var result = mapper.Map<XQueryResult<XAiProjectDto>>(queryResult);
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts a QueryResult of Entities to Dtos ...
|
|
/// <see cref="XAiMessageDto"/>
|
|
/// </summary>
|
|
/// <param name="queryResult"><see cref="XAiMessage"/></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<XQueryResult<XAiMessageDto>> ToDtoQueryResult(
|
|
XQueryResult<XAiMessage> queryResult,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
var result = mapper.Map<XQueryResult<XAiMessageDto>>(queryResult);
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts a QueryResult of Entities to Dtos ...
|
|
/// <see cref="XAiConversationDto"/>
|
|
/// </summary>
|
|
/// <param name="queryResult"><see cref="XAiConversation"/></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<XQueryResult<XAiConversationDto>> ToDtoQueryResult(
|
|
XQueryResult<XAiConversation> queryResult,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
var result = mapper.Map<XQueryResult<XAiConversationDto>>(queryResult);
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check Permission for Ai Actions ...
|
|
/// </summary>
|
|
/// <param name="projectId"></param>
|
|
/// <param name="userInfo"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<bool> HasPermission(
|
|
string projectId = null,
|
|
XUserClaimsInfoDto userInfo = null,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
var result =
|
|
!userInfo.IsNullOrDefault() &&
|
|
!userInfo.UserId.IsNullOrEmpty();
|
|
if (!result)
|
|
{
|
|
return result;
|
|
}
|
|
|
|
//
|
|
try
|
|
{
|
|
//
|
|
var person = await GetPerson(
|
|
ownerId: userInfo.UserId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
result = !person.IsNullOrDefault();
|
|
}
|
|
catch
|
|
{
|
|
result = false;
|
|
}
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prepare Prompt ...
|
|
/// </summary>
|
|
/// <param name="prompt"></param>
|
|
/// <param name="projectId"></param>
|
|
/// <param name="userInfo"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<string> PreparePrompt(
|
|
string prompt,
|
|
string projectId = null,
|
|
XUserClaimsInfoDto userInfo = null,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
var result = prompt;
|
|
|
|
//
|
|
// TODO: Complete this ...
|
|
|
|
//
|
|
return await Task.FromResult(result);
|
|
}
|
|
#endregion
|
|
|
|
//
|
|
#region Actions ...
|
|
/// <summary>
|
|
/// Ask a Question ...
|
|
/// </summary>
|
|
/// <param name="prompt"></param>
|
|
/// <param name="projectId"></param>
|
|
/// <param name="conversationId"></param>
|
|
/// <param name="userInfo"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<XAiMessageDto> Ask(
|
|
string prompt,
|
|
string projectId = null,
|
|
string conversationId = null,
|
|
XUserClaimsInfoDto userInfo = null,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
// Get All Requirements and also
|
|
// Prepare Ai Project / Conversations and also ChatHistory
|
|
// which Including Prompt ...
|
|
var requirements = await GetRequirements(
|
|
prompt: prompt,
|
|
userInfo: userInfo,
|
|
projectId: projectId,
|
|
conversationId: conversationId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
var isValid =
|
|
!requirements.IsNullOrDefault() &&
|
|
requirements.HasPermission;
|
|
if (!isValid)
|
|
{
|
|
XException.NotAllowed.Throw();
|
|
}
|
|
|
|
//
|
|
// Getting Response Message from LLM ...
|
|
var chatResult = await chatClient
|
|
.GetResponseAsync(
|
|
options: chatOptions,
|
|
messages: requirements.ChatHistory,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
// Handle Issued Chat Response ...
|
|
isValid =
|
|
!chatResult.IsNullOrDefault() &&
|
|
chatResult.FinishReason == ChatFinishReason.Stop;
|
|
if (!isValid)
|
|
{
|
|
XException.ActionFailed.Throw();
|
|
}
|
|
|
|
//
|
|
// Prepare Response Message ...
|
|
var responseMessage = new XAiMessage
|
|
{
|
|
OwnerId = userInfo.UserId,
|
|
Content = chatResult.Text,
|
|
CreatedOn = DateTime.UtcNow,
|
|
UpdatedAt = DateTime.UtcNow,
|
|
Role = XAiChatRole.Assistant,
|
|
ConversationId = requirements.AiConversation.Id
|
|
};
|
|
responseMessage = await aiMessageRepository.AddAsync(
|
|
saveChanges: true,
|
|
item: responseMessage
|
|
);
|
|
isValid = !responseMessage.IsNullOrDefault();
|
|
if (!isValid)
|
|
{
|
|
XException.InvalidData.Throw();
|
|
}
|
|
|
|
//
|
|
// Converts Entity to Dto ...
|
|
var result = await ToDto(
|
|
entity: responseMessage,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ask a Question in Streaming ...
|
|
/// </summary>
|
|
/// <param name="prompt"></param>
|
|
/// <param name="projectId"></param>
|
|
/// <param name="conversationId"></param>
|
|
/// <param name="userInfo"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async IAsyncEnumerable<XAiMessageUpdateDto> AskStream(
|
|
string prompt,
|
|
string projectId = null,
|
|
string conversationId = null,
|
|
XUserClaimsInfoDto userInfo = null,
|
|
[EnumeratorCancellation]
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
// Get All Requirements and also
|
|
// Prepare Ai Project / Conversations and also ChatHistory
|
|
// which Including Prompt ...
|
|
var requirements = await GetRequirements(
|
|
prompt: prompt,
|
|
userInfo: userInfo,
|
|
projectId: projectId,
|
|
conversationId: conversationId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
var isValid =
|
|
!requirements.IsNullOrDefault() &&
|
|
requirements.HasPermission;
|
|
if (!isValid)
|
|
{
|
|
XException.NotAllowed.Throw();
|
|
}
|
|
|
|
//
|
|
// Getting Response Message from LLM ...
|
|
var chatStream = chatClient
|
|
.GetStreamingResponseAsync(
|
|
options: chatOptions,
|
|
messages: requirements.ChatHistory,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
isValid = !chatStream.IsNull();
|
|
if (!isValid)
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
//
|
|
// Create an Empty Message for ID ...
|
|
var responseMessage = new XAiMessage
|
|
{
|
|
OwnerId = userInfo.UserId,
|
|
Role = XAiChatRole.Assistant,
|
|
CreatedOn = DateTime.UtcNow,
|
|
UpdatedAt = DateTime.UtcNow,
|
|
Content = "Streaming Content",
|
|
ConversationId = requirements.AiConversation.Id
|
|
};
|
|
responseMessage = await aiMessageRepository.AddAsync(
|
|
saveChanges: true,
|
|
item: responseMessage
|
|
);
|
|
|
|
//
|
|
// Loop through Async Enumerable ...
|
|
string content = string.Empty;
|
|
var sequence = DateTime.UtcNow.ToTimestamp();
|
|
await foreach(var msg in chatStream)
|
|
{
|
|
//
|
|
// Crucially, check the cancellation token before continuing
|
|
// Throwing OperationCanceledException is the standard way to signal cancellation.
|
|
if (cancellationToken.IsCancellationRequested)
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
//
|
|
// Append recieved data to Content ...
|
|
content += msg.Text;
|
|
|
|
//
|
|
// Crate Message Update Model ...
|
|
var item = new XAiMessageUpdateDto
|
|
{
|
|
Sequense = sequence,
|
|
Content = msg.Text,
|
|
Id = responseMessage.Id,
|
|
OwnerId = userInfo.UserId,
|
|
UpdatedAt = DateTime.UtcNow,
|
|
Role = XAiChatRole.Assistant,
|
|
ConversationId = requirements.AiConversation.Id
|
|
};
|
|
|
|
//
|
|
sequence++;
|
|
|
|
//
|
|
yield return item;
|
|
}
|
|
|
|
//
|
|
// Update Response Message Entity ...
|
|
responseMessage.Content = content;
|
|
responseMessage.UpdatedAt = DateTime.UtcNow;
|
|
responseMessage = await aiMessageRepository.UpdateAsync(
|
|
saveChanges: true,
|
|
item: responseMessage,
|
|
id: responseMessage.Id
|
|
);
|
|
isValid = !responseMessage.IsNullOrDefault();
|
|
if (!isValid)
|
|
{
|
|
XException.ActionFailed.Throw();
|
|
}
|
|
|
|
//
|
|
// End Async Enumerable ...
|
|
yield break;
|
|
}
|
|
#endregion
|
|
|
|
//
|
|
#region Private ...
|
|
/// <summary>
|
|
/// Access Person class based on OwnerId ...
|
|
/// <see cref="XPersonDto"/>
|
|
/// </summary>
|
|
/// <param name="ownerId"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
private async Task<XPersonDto> GetPerson(
|
|
string ownerId,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
XPersonDto result = null;
|
|
|
|
//
|
|
if (!ownerId.IsNullOrEmpty())
|
|
{
|
|
//
|
|
var device = identityProvider.GetDevice();
|
|
var userInfo = await identityProvider.GetUserInfo(
|
|
device: device,
|
|
userSelectByParam: ownerId
|
|
);
|
|
if (userInfo.IsNullOrDefault())
|
|
{
|
|
XException.NotFound.Throw();
|
|
}
|
|
|
|
//
|
|
result = userInfo.ToXPersonDto();
|
|
}
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieve Specified Project or Default ...
|
|
/// </summary>
|
|
/// <param name="projectId"></param>
|
|
/// <param name="userInfo"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
private async Task<XAiProjectDto> GetProject(
|
|
string projectId = null,
|
|
XUserClaimsInfoDto userInfo = null,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
// Validate UserInfo ...
|
|
var isValid =
|
|
!userInfo.IsNullOrDefault() &&
|
|
!userInfo.UserId.IsNullOrEmpty();
|
|
if (!isValid)
|
|
{
|
|
XException.NotAllowed.Throw();
|
|
}
|
|
|
|
//
|
|
// Check UserInfo ...
|
|
var personDto = await GetPerson(
|
|
ownerId: userInfo.UserId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
isValid = !personDto.IsNullOrDefault();
|
|
if (!isValid)
|
|
{
|
|
XException.NotFound.Throw();
|
|
}
|
|
|
|
//
|
|
XAiProject project = null;
|
|
|
|
//
|
|
// Check Default Project is Exists or not ...
|
|
var defaultProject = await aiProjectRepository.FindOneAsync(
|
|
containsDetail: true,
|
|
ignoreSoftDeleteds: true,
|
|
whereClause: x =>
|
|
x.OwnerId == userInfo.UserId &&
|
|
x.Title == XAiProjectEnum.Default.GetStringValue()
|
|
);
|
|
var isExists = !defaultProject.IsNullOrDefault();
|
|
if (!isExists)
|
|
{
|
|
//
|
|
// Create Default Project ...
|
|
defaultProject = new XAiProject
|
|
{
|
|
Prompt = string.Empty,
|
|
OwnerId = userInfo.UserId,
|
|
Description = string.Empty,
|
|
CreatedOn = DateTime.UtcNow,
|
|
UpdatedAt = DateTime.UtcNow,
|
|
Title = XAiProjectEnum.Default.GetStringValue(),
|
|
};
|
|
|
|
//
|
|
defaultProject = await aiProjectRepository.AddAsync(
|
|
saveChanges: true,
|
|
item: defaultProject
|
|
);
|
|
|
|
//
|
|
isExists = !defaultProject.IsNullOrDefault();
|
|
}
|
|
|
|
//
|
|
// Validate Project ID ...
|
|
if (projectId.IsNullOrEmpty())
|
|
{
|
|
//
|
|
// Default Project ...
|
|
project = defaultProject;
|
|
projectId = defaultProject.Id.ToString();
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Special Project ...
|
|
project = await aiProjectRepository.FindOneAsync(
|
|
containsDetail: false,
|
|
ignoreSoftDeleteds: true,
|
|
whereClause: x =>
|
|
x.Id.ToString() == projectId &&
|
|
x.OwnerId == userInfo.UserId
|
|
);
|
|
isExists = !project.IsNullOrDefault();
|
|
if (!isExists)
|
|
{
|
|
//
|
|
// If Not Exists Use Default ...
|
|
project = defaultProject;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Converts to Dto ...
|
|
var result = await ToDto(
|
|
entity: project,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieve Conversation for Asking ...
|
|
/// </summary>
|
|
/// <param name="projectId"></param>
|
|
/// <param name="conversationId"></param>
|
|
/// <param name="userInfo"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
private async Task<XAiConversationDto> GetConversation(
|
|
string projectId = null,
|
|
string conversationId = null,
|
|
XUserClaimsInfoDto userInfo = null,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
// Validate User Info ...
|
|
var isValid = await HasPermission(
|
|
userInfo: userInfo,
|
|
projectId: projectId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
if (!isValid)
|
|
{
|
|
XException.NotAllowed.Throw();
|
|
}
|
|
|
|
//
|
|
var project = await GetProject(
|
|
userInfo: userInfo,
|
|
projectId: projectId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
isValid = !project.IsNullOrDefault();
|
|
if (!isValid)
|
|
{
|
|
XException.NotFound.Throw();
|
|
}
|
|
|
|
//
|
|
// Check Conversation Exists ...
|
|
var result =
|
|
project.Conversations
|
|
.FirstOrDefault(x =>
|
|
x.OwnerId == userInfo.UserId &&
|
|
x.Id.ToString() == conversationId
|
|
);
|
|
var isExists =
|
|
!result.IsNullOrDefault() &&
|
|
!conversationId.IsNullOrEmpty();
|
|
if (!isExists)
|
|
{
|
|
//
|
|
var title = $"Conversation {Guid.NewGuid()}";
|
|
var conversationEntity = new XAiConversation
|
|
{
|
|
Title = title,
|
|
ProjectId = project.Id,
|
|
OwnerId = userInfo.UserId,
|
|
CreatedOn = DateTime.UtcNow,
|
|
UpdatedAt = DateTime.UtcNow,
|
|
};
|
|
|
|
//
|
|
conversationEntity = await aiConversationRepository.AddAsync(
|
|
saveChanges: true,
|
|
item: conversationEntity
|
|
);
|
|
isExists = !conversationEntity.IsNullOrDefault();
|
|
if (!isExists)
|
|
{
|
|
XException.NotFound.Throw();
|
|
}
|
|
|
|
//
|
|
result = await ToDto(
|
|
entity: conversationEntity,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
}
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prepare Requirements for Asking a Prompt from Model ...
|
|
/// </summary>
|
|
/// <param name="prompt"></param>
|
|
/// <param name="projectId"></param>
|
|
/// <param name="conversationId"></param>
|
|
/// <param name="userInfo"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
private async Task<XAiRequirementDto> GetRequirements(
|
|
string prompt,
|
|
string projectId = null,
|
|
string conversationId = null,
|
|
XUserClaimsInfoDto userInfo = null,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
var result = new XAiRequirementDto();
|
|
|
|
//
|
|
// Access Project ...
|
|
var project = await GetProject(
|
|
userInfo: userInfo,
|
|
projectId: projectId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
var isValid = !project.IsNullOrDefault();
|
|
if (!isValid)
|
|
{
|
|
XException.NotFound.Throw();
|
|
}
|
|
|
|
//
|
|
result.AiProject = project;
|
|
|
|
//
|
|
// Check Project is Default Project or not ...
|
|
var isDefaultProject = project.Title == XAiProjectEnum.Default.GetStringValue();
|
|
|
|
//
|
|
result.IsDefaultProject = isDefaultProject;
|
|
|
|
//
|
|
// Access Conversation ...
|
|
var conversation = await GetConversation(
|
|
userInfo: userInfo,
|
|
projectId: projectId,
|
|
conversationId: conversationId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
isValid = !conversation.IsNullOrDefault();
|
|
if (!isValid)
|
|
{
|
|
XException.NotFound.Throw();
|
|
}
|
|
|
|
//
|
|
// Check First Question or not ...
|
|
var isFirstConversation = !conversation.Messages.HasChild();
|
|
|
|
//
|
|
result.AiConversation = conversation;
|
|
result.IsFirstConversation = isFirstConversation;
|
|
|
|
//
|
|
// Check Permissions ...
|
|
isValid = await HasPermission(
|
|
userInfo: userInfo,
|
|
projectId: projectId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
if (!isValid)
|
|
{
|
|
XException.NotAllowed.Throw();
|
|
}
|
|
|
|
//
|
|
result.HasPermission = isValid;
|
|
|
|
//
|
|
// Check if First Question ...
|
|
if (isFirstConversation)
|
|
{
|
|
//
|
|
// Add Introduction Prompt ...
|
|
var aiMessage = new XAiMessage
|
|
{
|
|
OwnerId = userInfo.UserId,
|
|
Role = XAiChatRole.System,
|
|
CreatedOn = DateTime.UtcNow,
|
|
UpdatedAt = DateTime.UtcNow,
|
|
ConversationId = conversation.Id,
|
|
Content = Configuration.Introduction,
|
|
};
|
|
aiMessage = await aiMessageRepository.AddAsync(
|
|
item: aiMessage,
|
|
saveChanges: true
|
|
);
|
|
isValid = !aiMessage.IsNullOrDefault();
|
|
if (!isValid)
|
|
{
|
|
XException.ActionFailed.Throw();
|
|
}
|
|
|
|
//
|
|
result.ChatHistory.Add(
|
|
new ChatMessage(
|
|
content: Configuration.Introduction,
|
|
role: XAiChatRole.System.ToChatRole()
|
|
)
|
|
);
|
|
|
|
//
|
|
// Add Project Prompt, if not Default Project ...
|
|
if (!isDefaultProject)
|
|
{
|
|
//
|
|
aiMessage = new XAiMessage
|
|
{
|
|
Content = project.Prompt,
|
|
OwnerId = userInfo.UserId,
|
|
Role = XAiChatRole.System,
|
|
CreatedOn = DateTime.UtcNow,
|
|
UpdatedAt = DateTime.UtcNow,
|
|
ConversationId = conversation.Id,
|
|
};
|
|
aiMessage = await aiMessageRepository.AddAsync(
|
|
item: aiMessage,
|
|
saveChanges: true
|
|
);
|
|
isValid = !aiMessage.IsNullOrDefault();
|
|
if (!isValid)
|
|
{
|
|
XException.ActionFailed.Throw();
|
|
}
|
|
|
|
//
|
|
result.ChatHistory.Add(
|
|
new ChatMessage(
|
|
content: project.Prompt,
|
|
role: XAiChatRole.System.ToChatRole()
|
|
)
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Complete Chat History ...
|
|
var orderedByTimeMessage = conversation.Messages
|
|
.OrderBy(x => x.CreatedOn)
|
|
.AsEnumerable();
|
|
foreach (var msg in orderedByTimeMessage)
|
|
{
|
|
//
|
|
result.ChatHistory.Add(
|
|
new ChatMessage(
|
|
content: msg.Content,
|
|
role: msg.Role.ToChatRole()
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Prepare Prompt ...
|
|
prompt = await PreparePrompt(
|
|
prompt: prompt,
|
|
userInfo: userInfo,
|
|
projectId: projectId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
if (result.ChatHistory.IsNull())
|
|
{
|
|
result.ChatHistory = new List<ChatMessage>();
|
|
}
|
|
|
|
//
|
|
// Prepare Prompt XAiMessage ...
|
|
var promptAiMessage = new XAiMessage
|
|
{
|
|
Content = prompt,
|
|
OwnerId = userInfo.UserId,
|
|
Role = XAiChatRole.User,
|
|
CreatedOn = DateTime.UtcNow,
|
|
UpdatedAt = DateTime.UtcNow,
|
|
ConversationId = conversation.Id,
|
|
};
|
|
|
|
//
|
|
// Add Prompt Message to Database ...
|
|
promptAiMessage = await aiMessageRepository.AddAsync(
|
|
saveChanges: true,
|
|
item: promptAiMessage
|
|
);
|
|
isValid = !promptAiMessage.IsNullOrDefault();
|
|
if (!isValid)
|
|
{
|
|
XException.InvalidData.Throw();
|
|
}
|
|
|
|
//
|
|
// Update Conversation ...
|
|
var conversationEntity = await aiConversationRepository
|
|
.GetAsync(id: result.AiConversation.Id);
|
|
conversation = mapper.Map<XAiConversationDto>(conversationEntity);
|
|
result.AiConversation = conversation;
|
|
|
|
//
|
|
result.ChatHistory.Add(
|
|
new ChatMessage(
|
|
content: promptAiMessage.Content,
|
|
role: promptAiMessage.Role.ToChatRole()
|
|
)
|
|
);
|
|
|
|
//
|
|
return result;
|
|
}
|
|
#endregion
|
|
}
|
|
} |