feat: 完成用量统计功能模块
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
namespace Yi.Framework.AiHub.Application.Contracts.Dtos;
|
||||
using SqlSugar;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Dtos;
|
||||
|
||||
namespace Yi.Framework.AiHub.Application.Contracts.Dtos;
|
||||
|
||||
public class MessageInputDto
|
||||
{
|
||||
public string Content { get; set; }
|
||||
public string Role { get; set; }
|
||||
public decimal DeductCost { get; set; }
|
||||
public decimal TotalTokens { get; set; }
|
||||
public string ModelId { get; set; }
|
||||
public string Remark { get; set; }
|
||||
public string? Remark { get; set; }
|
||||
|
||||
public TokenUsage? TokenUsage { get; set; }
|
||||
}
|
||||
@@ -12,7 +12,9 @@ using Volo.Abp.Application.Services;
|
||||
using Volo.Abp.Users;
|
||||
using Yi.Framework.AiHub.Application.Contracts.Dtos;
|
||||
using Yi.Framework.AiHub.Domain.Entities;
|
||||
using Yi.Framework.AiHub.Domain.Entities.Model;
|
||||
using Yi.Framework.AiHub.Domain.Managers;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Dtos;
|
||||
using Yi.Framework.Rbac.Application.Contracts.IServices;
|
||||
using Yi.Framework.Rbac.Domain.Shared.Dtos;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
@@ -28,15 +30,17 @@ public class AiChatService : ApplicationService
|
||||
private readonly AiMessageManager _aiMessageManager;
|
||||
private readonly ISqlSugarRepository<AiModelEntity> _aiModelRepository;
|
||||
private readonly AiBlacklistManager _aiBlacklistManager;
|
||||
private readonly UsageStatisticsManager _usageStatisticsManager;
|
||||
|
||||
public AiChatService(IHttpContextAccessor httpContextAccessor,
|
||||
AiMessageManager aiMessageManager, AiBlacklistManager aiBlacklistManager,
|
||||
ISqlSugarRepository<AiModelEntity> aiModelRepository)
|
||||
ISqlSugarRepository<AiModelEntity> aiModelRepository, UsageStatisticsManager usageStatisticsManager)
|
||||
{
|
||||
this._httpContextAccessor = httpContextAccessor;
|
||||
_aiMessageManager = aiMessageManager;
|
||||
_aiBlacklistManager = aiBlacklistManager;
|
||||
_aiModelRepository = aiModelRepository;
|
||||
_usageStatisticsManager = usageStatisticsManager;
|
||||
}
|
||||
|
||||
|
||||
@@ -60,21 +64,21 @@ public class AiChatService : ApplicationService
|
||||
public async Task<List<ModelGetListOutput>> GetModelAsync()
|
||||
{
|
||||
var output = await _aiModelRepository._DbQueryable
|
||||
.OrderByDescending(x=>x.OrderNum)
|
||||
.OrderByDescending(x => x.OrderNum)
|
||||
.Select(x => new ModelGetListOutput
|
||||
{
|
||||
Id = x.Id,
|
||||
Category = "chat",
|
||||
ModelName = x.Name,
|
||||
ModelDescribe = x.Description,
|
||||
ModelPrice = 0,
|
||||
ModelType = "1",
|
||||
ModelShow = "0",
|
||||
SystemPrompt = null,
|
||||
ApiHost = null,
|
||||
ApiKey = null,
|
||||
Remark = x.Description
|
||||
}).ToListAsync();
|
||||
{
|
||||
Id = x.Id,
|
||||
Category = "chat",
|
||||
ModelName = x.Name,
|
||||
ModelDescribe = x.Description,
|
||||
ModelPrice = 0,
|
||||
ModelType = "1",
|
||||
ModelShow = "0",
|
||||
SystemPrompt = null,
|
||||
ApiHost = null,
|
||||
ApiKey = null,
|
||||
Remark = x.Description
|
||||
}).ToListAsync();
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -128,13 +132,15 @@ public class AiChatService : ApplicationService
|
||||
|
||||
var gateWay = LazyServiceProvider.GetRequiredService<AiGateWayManager>();
|
||||
var completeChatResponse = gateWay.CompleteChatAsync(input.Model, history, cancellationToken);
|
||||
var tokenUsage = new TokenUsage();
|
||||
await using var writer = new StreamWriter(response.Body, Encoding.UTF8, leaveOpen: true);
|
||||
|
||||
|
||||
//缓存队列算法
|
||||
|
||||
// 创建一个队列来缓存消息
|
||||
var messageQueue = new ConcurrentQueue<string>();
|
||||
|
||||
StringBuilder backupSystemContent = new StringBuilder();
|
||||
// 设置输出速率(例如每50毫秒输出一次)
|
||||
var outputInterval = TimeSpan.FromMilliseconds(100);
|
||||
// 标记是否完成接收
|
||||
@@ -161,33 +167,49 @@ public class AiChatService : ApplicationService
|
||||
|
||||
await foreach (var data in completeChatResponse)
|
||||
{
|
||||
var model = MapToMessage(input.Model, data);
|
||||
if (data.IsFinish)
|
||||
{
|
||||
tokenUsage = data.TokenUsage;
|
||||
}
|
||||
|
||||
var model = MapToMessage(input.Model, data.Content);
|
||||
var message = JsonConvert.SerializeObject(model, new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||
});
|
||||
|
||||
backupSystemContent.Append(data.Content);
|
||||
// 将消息加入队列而不是直接写入
|
||||
messageQueue.Enqueue($"data: {message}\n");
|
||||
}
|
||||
|
||||
// 标记完成并发送结束标记
|
||||
isComplete = true;
|
||||
|
||||
//断开连接
|
||||
messageQueue.Enqueue("data: done\n");
|
||||
// 标记完成并发送结束标记
|
||||
isComplete = true;
|
||||
|
||||
await outputTask;
|
||||
if (CurrentUser.IsAuthenticated && input.SessionId.HasValue)
|
||||
{
|
||||
await _aiMessageManager.CreateMessageAsync(CurrentUser.GetId(), input.SessionId.Value, new MessageInputDto
|
||||
{
|
||||
Content = input.Messages.LastOrDefault().Content,
|
||||
Role = input.Messages.LastOrDefault().Role,
|
||||
DeductCost = 0,
|
||||
TotalTokens = 0,
|
||||
ModelId = input.Model,
|
||||
Remark = null
|
||||
});
|
||||
await _aiMessageManager.CreateUserMessageAsync(CurrentUser.GetId(), input.SessionId.Value,
|
||||
new MessageInputDto
|
||||
{
|
||||
Content = input.Messages.LastOrDefault()
|
||||
.Content,
|
||||
ModelId = input.Model,
|
||||
TokenUsage = tokenUsage,
|
||||
});
|
||||
|
||||
await _aiMessageManager.CreateSystemMessageAsync(CurrentUser.GetId(), input.SessionId.Value,
|
||||
new MessageInputDto
|
||||
{
|
||||
Content = backupSystemContent.ToString(),
|
||||
ModelId = input.Model,
|
||||
TokenUsage = tokenUsage
|
||||
});
|
||||
|
||||
await _usageStatisticsManager.SetUsageAsync(CurrentUser.GetId(), input.Model, tokenUsage.InputTokenCount,
|
||||
tokenUsage.OutputTokenCount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,9 +271,9 @@ public class AiChatService : ApplicationService
|
||||
SystemFingerprint = "",
|
||||
Usage = new Usage
|
||||
{
|
||||
PromptTokens = 75,
|
||||
CompletionTokens = 25,
|
||||
TotalTokens = 100,
|
||||
PromptTokens = 0,
|
||||
CompletionTokens = 0,
|
||||
TotalTokens = 0,
|
||||
PromptTokensDetails = new()
|
||||
{
|
||||
AudioTokens = 0,
|
||||
|
||||
@@ -7,6 +7,7 @@ using Volo.Abp.Application.Services;
|
||||
using Volo.Abp.Users;
|
||||
using Yi.Framework.AiHub.Application.Contracts.Dtos;
|
||||
using Yi.Framework.AiHub.Domain.Entities;
|
||||
using Yi.Framework.AiHub.Domain.Entities.Chat;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
namespace Yi.Framework.AiHub.Application.Services;
|
||||
@@ -34,7 +35,7 @@ public class MessageService : ApplicationService
|
||||
var entities = await _repository._DbQueryable
|
||||
.Where(x => x.SessionId == input.SessionId)
|
||||
.Where(x=>x.UserId == userId)
|
||||
.OrderByDescending(x => x.Id)
|
||||
.OrderBy(x => x.Id)
|
||||
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
|
||||
return new PagedResultDto<MessageDto>(total, entities.Adapt<List<MessageDto>>());
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using Volo.Abp.Domain.Repositories;
|
||||
using Volo.Abp.Users;
|
||||
using Yi.Framework.AiHub.Application.Contracts.Dtos;
|
||||
using Yi.Framework.AiHub.Domain.Entities;
|
||||
using Yi.Framework.AiHub.Domain.Entities.Chat;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
namespace Yi.Framework.AiHub.Application.Services;
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Yi.Framework.AiHub.Domain.Shared.Dtos;
|
||||
|
||||
public class CompleteChatResponse
|
||||
{
|
||||
public TokenUsage? TokenUsage { get; set; }
|
||||
public bool IsFinish { get; set; }
|
||||
public string? Content { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Yi.Framework.AiHub.Domain.Shared.Dtos;
|
||||
|
||||
public class TokenUsage
|
||||
{
|
||||
public int OutputTokenCount { get; set; }
|
||||
|
||||
public int InputTokenCount { get; set; }
|
||||
|
||||
public int TotalTokenCount { get; set; }
|
||||
}
|
||||
@@ -5,6 +5,6 @@ namespace Yi.Framework.AiHub.Domain.AiChat;
|
||||
|
||||
public interface IChatService
|
||||
{
|
||||
public IAsyncEnumerable<string> CompleteChatAsync(AiModelDescribe aiModelDescribe, List<ChatMessage> messages,
|
||||
public IAsyncEnumerable<CompleteChatResponse> CompleteChatAsync(AiModelDescribe aiModelDescribe, List<ChatMessage> messages,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
@@ -12,7 +12,8 @@ public class AzureChatService : IChatService
|
||||
{
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<string> CompleteChatAsync(AiModelDescribe aiModelDescribe, List<ChatMessage> messages,
|
||||
public async IAsyncEnumerable<CompleteChatResponse> CompleteChatAsync(AiModelDescribe aiModelDescribe,
|
||||
List<ChatMessage> messages,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
var endpoint = new Uri(aiModelDescribe.Endpoint);
|
||||
@@ -32,9 +33,28 @@ public class AzureChatService : IChatService
|
||||
|
||||
await foreach (StreamingChatCompletionUpdate update in response)
|
||||
{
|
||||
var result = new CompleteChatResponse();
|
||||
var isFinish = update.Usage?.OutputTokenCount is not null;
|
||||
if (isFinish)
|
||||
{
|
||||
result.IsFinish = true;
|
||||
result.TokenUsage = new TokenUsage
|
||||
{
|
||||
OutputTokenCount = update.Usage.OutputTokenCount,
|
||||
InputTokenCount = update.Usage.InputTokenCount,
|
||||
TotalTokenCount = update.Usage.TotalTokenCount
|
||||
};
|
||||
}
|
||||
|
||||
foreach (ChatMessageContentPart updatePart in update.ContentUpdate)
|
||||
{
|
||||
yield return updatePart.Text;
|
||||
result.Content = updatePart.Text;
|
||||
yield return result;
|
||||
}
|
||||
|
||||
if (isFinish)
|
||||
{
|
||||
yield return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@ public class AzureRestChatService : IChatService
|
||||
{
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<string> CompleteChatAsync(AiModelDescribe aiModelDescribe, List<ChatMessage> messages,
|
||||
public async IAsyncEnumerable<CompleteChatResponse> CompleteChatAsync(AiModelDescribe aiModelDescribe,
|
||||
List<ChatMessage> messages,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
// 设置API URL
|
||||
@@ -61,51 +62,65 @@ public class AzureRestChatService : IChatService
|
||||
var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken);
|
||||
// 从流中读取数据并输出到控制台
|
||||
using var streamReader = new StreamReader(responseStream);
|
||||
string line;
|
||||
while ((line = await streamReader.ReadLineAsync(cancellationToken)) != null)
|
||||
while (await streamReader.ReadLineAsync(cancellationToken) is { } line)
|
||||
{
|
||||
var result = GetContent(line);
|
||||
if (result is not null)
|
||||
var result = new CompleteChatResponse();
|
||||
try
|
||||
{
|
||||
yield return result;
|
||||
var jsonObj = MapToJObject(line);
|
||||
var content = GetContent(jsonObj);
|
||||
var tokenUsage = GetTokenUsage(jsonObj);
|
||||
result= new CompleteChatResponse
|
||||
{
|
||||
TokenUsage = tokenUsage,
|
||||
IsFinish = tokenUsage is not null,
|
||||
Content = content
|
||||
};
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine("解析失败");
|
||||
}
|
||||
|
||||
yield return result;
|
||||
}
|
||||
}
|
||||
|
||||
private string? GetContent(string line)
|
||||
private JObject? MapToJObject(string line)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
return null;
|
||||
string prefix = "data: ";
|
||||
line = line.Substring(prefix.Length);
|
||||
return JObject.Parse(line);
|
||||
}
|
||||
|
||||
private string? GetContent(JObject? jsonObj)
|
||||
{
|
||||
var contentToken = jsonObj.SelectToken("choices[0].delta.content");
|
||||
if (contentToken != null && contentToken.Type != JTokenType.Null)
|
||||
{
|
||||
return contentToken.ToString();
|
||||
}
|
||||
|
||||
try
|
||||
return null;
|
||||
}
|
||||
|
||||
private TokenUsage? GetTokenUsage(JObject? jsonObj)
|
||||
{
|
||||
var usage = jsonObj.SelectToken("usage");
|
||||
if (usage is not null && usage.Type != JTokenType.Null)
|
||||
{
|
||||
// 解析为JObject
|
||||
var jsonObj = JObject.Parse(line);
|
||||
var content = jsonObj["choices"][0]["delta"]["content"].ToString();
|
||||
return content;
|
||||
// // 判断choices是否存在且是数组,并且有元素
|
||||
// if (jsonObj.TryGetValue("choices", out var choicesToken) && choicesToken is JArray choicesArray &&
|
||||
// choicesArray.Count > 0)
|
||||
// {
|
||||
// var firstChoice = choicesArray[0] as JObject;
|
||||
// // 判断delta字段是否存在
|
||||
// if (firstChoice.TryGetValue("delta", out var deltaToken))
|
||||
// {
|
||||
// // 获取content字段
|
||||
// if (deltaToken.Type == JTokenType.Object && ((JObject)deltaToken).TryGetValue("content", out var contentToken))
|
||||
// {
|
||||
// return contentToken.ToString();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// 解析失败
|
||||
return null;
|
||||
var result = new TokenUsage()
|
||||
{
|
||||
OutputTokenCount = usage["completion_tokens"].ToObject<int>(),
|
||||
InputTokenCount = usage["prompt_tokens"].ToObject<int>(),
|
||||
TotalTokenCount = usage["total_tokens"].ToObject<int>()
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
using SqlSugar;
|
||||
using Mapster;
|
||||
using SqlSugar;
|
||||
using Volo.Abp.Domain.Entities.Auditing;
|
||||
using Yi.Framework.AiHub.Domain.Entities.ValueObjects;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Dtos;
|
||||
|
||||
namespace Yi.Framework.AiHub.Domain.Entities;
|
||||
namespace Yi.Framework.AiHub.Domain.Entities.Chat;
|
||||
|
||||
[SugarTable("Ai_Message")]
|
||||
[SugarIndex($"index_{{table}}_{nameof(UserId)}_{nameof(SessionId)}",
|
||||
@@ -14,21 +17,32 @@ public class MessageAggregateRoot : FullAuditedAggregateRoot<Guid>
|
||||
{
|
||||
}
|
||||
|
||||
public MessageAggregateRoot(Guid userId, Guid sessionId, string content, string role, string modelId)
|
||||
public MessageAggregateRoot(Guid userId, Guid sessionId, string content, string role, string modelId,
|
||||
TokenUsage? tokenUsage)
|
||||
{
|
||||
UserId = userId;
|
||||
SessionId = sessionId;
|
||||
Content = content;
|
||||
Role = role;
|
||||
ModelId = modelId;
|
||||
if (tokenUsage is not null)
|
||||
{
|
||||
this.TokenUsage = tokenUsage.Adapt<TokenUsageValueObject>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Guid UserId { get; set; }
|
||||
public Guid SessionId { get; set; }
|
||||
|
||||
[SugarColumn(ColumnDataType = StaticConfig.CodeFirst_BigString)]
|
||||
public string Content { get; set; }
|
||||
|
||||
public string Role { get; set; }
|
||||
public decimal DeductCost { get; set; }
|
||||
public decimal TotalTokens { get; set; }
|
||||
public string ModelId { get; set; }
|
||||
public string Remark { get; set; }
|
||||
public string? Remark { get; set; }
|
||||
|
||||
[SugarColumn(IsOwnsOne = true)] public TokenUsageValueObject TokenUsage { get; set; }
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using SqlSugar;
|
||||
using Volo.Abp.Domain.Entities.Auditing;
|
||||
|
||||
namespace Yi.Framework.AiHub.Domain.Entities;
|
||||
namespace Yi.Framework.AiHub.Domain.Entities.Chat;
|
||||
|
||||
[SugarTable("Ai_Session")]
|
||||
[SugarIndex($"index_{{table}}_{nameof(UserId)}",$"{nameof(UserId)}", OrderByType.Asc)]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
using Volo.Abp.Domain.Entities.Auditing;
|
||||
using Yi.Framework.Core.Data;
|
||||
|
||||
namespace Yi.Framework.AiHub.Domain.Entities;
|
||||
namespace Yi.Framework.AiHub.Domain.Entities.Model;
|
||||
|
||||
/// <summary>
|
||||
/// ai应用
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using SqlSugar;
|
||||
using Volo.Abp.Domain.Entities;
|
||||
using Volo.Abp.Domain.Entities.Auditing;
|
||||
using Yi.Framework.Core.Data;
|
||||
|
||||
namespace Yi.Framework.AiHub.Domain.Entities;
|
||||
namespace Yi.Framework.AiHub.Domain.Entities.Model;
|
||||
|
||||
/// <summary>
|
||||
/// ai模型定义
|
||||
|
||||
@@ -9,6 +9,16 @@ namespace Yi.Framework.AiHub.Domain.Entities;
|
||||
[SugarTable("Ai_UsageStatistics")]
|
||||
public class UsageStatisticsAggregateRoot : FullAuditedAggregateRoot<Guid>
|
||||
{
|
||||
public UsageStatisticsAggregateRoot()
|
||||
{
|
||||
}
|
||||
|
||||
public UsageStatisticsAggregateRoot(Guid userId, string modelId)
|
||||
{
|
||||
UserId = userId;
|
||||
ModelId = modelId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户id
|
||||
/// </summary>
|
||||
@@ -19,18 +29,34 @@ public class UsageStatisticsAggregateRoot : FullAuditedAggregateRoot<Guid>
|
||||
/// </summary>
|
||||
public string ModelId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 输入使用token使用
|
||||
/// </summary>
|
||||
public decimal InputTokens { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 输出使用token使用
|
||||
/// </summary>
|
||||
public decimal OutputTokens { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 对话次数
|
||||
/// </summary>
|
||||
public int Number { get; set; }
|
||||
public int UsageTotalNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 使用输出token总数
|
||||
/// </summary>
|
||||
public int UsageOutputTokenCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 使用输入总数
|
||||
/// </summary>
|
||||
public int UsageInputTokenCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总token使用数量
|
||||
/// </summary>
|
||||
public int TotalTokenCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 新增一次聊天统计
|
||||
/// </summary>
|
||||
public void AddOnceChat(int inputTokenCount, int outputTokenCount)
|
||||
{
|
||||
UsageTotalNumber += 1;
|
||||
UsageOutputTokenCount += outputTokenCount;
|
||||
UsageInputTokenCount += inputTokenCount;
|
||||
TotalTokenCount += (outputTokenCount + inputTokenCount);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Yi.Framework.AiHub.Domain.Entities.ValueObjects;
|
||||
|
||||
public class TokenUsageValueObject
|
||||
{
|
||||
public int OutputTokenCount { get; set; }
|
||||
|
||||
public int InputTokenCount { get; set; }
|
||||
|
||||
public int TotalTokenCount { get; set; }
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using OpenAI.Chat;
|
||||
using Volo.Abp.Domain.Services;
|
||||
using Yi.Framework.AiHub.Domain.AiChat;
|
||||
using Yi.Framework.AiHub.Domain.Entities;
|
||||
using Yi.Framework.AiHub.Domain.Entities.Model;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Dtos;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
@@ -57,7 +58,7 @@ public class AiGateWayManager : DomainService
|
||||
/// <param name="messages"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async IAsyncEnumerable<string> CompleteChatAsync(string modelId, List<ChatMessage> messages,
|
||||
public async IAsyncEnumerable<CompleteChatResponse> CompleteChatAsync(string modelId, List<ChatMessage> messages,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
var modelDescribe = await GetModelAsync(modelId);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Volo.Abp.Users;
|
||||
using Yi.Framework.AiHub.Application.Contracts.Dtos;
|
||||
using Yi.Framework.AiHub.Domain.Entities;
|
||||
using Yi.Framework.AiHub.Domain.Entities.Chat;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
namespace Yi.Framework.AiHub.Domain.Managers;
|
||||
@@ -16,15 +17,30 @@ public class AiMessageManager : DomainService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建消息
|
||||
/// 创建系统消息
|
||||
/// </summary>
|
||||
/// <param name="sessionId"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public async Task CreateMessageAsync(Guid userId, Guid sessionId, MessageInputDto input)
|
||||
public async Task CreateSystemMessageAsync(Guid userId, Guid sessionId, MessageInputDto input)
|
||||
{
|
||||
var message = new MessageAggregateRoot(userId, sessionId, input.Content, input.Role, input.ModelId);
|
||||
input.Role = "system";
|
||||
var message = new MessageAggregateRoot(userId, sessionId, input.Content, input.Role, input.ModelId,input.TokenUsage);
|
||||
await _repository.InsertAsync(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建系统消息
|
||||
/// </summary>
|
||||
/// <param name="sessionId"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public async Task CreateUserMessageAsync(Guid userId, Guid sessionId, MessageInputDto input)
|
||||
{
|
||||
input.Role = "user";
|
||||
var message = new MessageAggregateRoot(userId, sessionId, input.Content, input.Role, input.ModelId,input.TokenUsage);
|
||||
await _repository.InsertAsync(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using Medallion.Threading;
|
||||
using Volo.Abp.Domain.Services;
|
||||
using Yi.Framework.AiHub.Domain.Entities;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
public class UsageStatisticsManager : DomainService
|
||||
{
|
||||
private readonly ISqlSugarRepository<UsageStatisticsAggregateRoot> _repository;
|
||||
|
||||
private IDistributedLockProvider DistributedLock =>
|
||||
LazyServiceProvider.LazyGetRequiredService<IDistributedLockProvider>();
|
||||
|
||||
public async Task SetUsageAsync(Guid userId, string modelId, int inputTokenCount, int outputTokenCount)
|
||||
{
|
||||
await using (await DistributedLock.AcquireLockAsync($"UsageStatistics:{userId.ToString()}"))
|
||||
{
|
||||
var entity = await _repository._DbQueryable.FirstAsync(x => x.UserId == userId && x.ModelId == modelId);
|
||||
//存在数据,更细
|
||||
if (entity is not null)
|
||||
{
|
||||
entity.AddOnceChat(inputTokenCount, outputTokenCount);
|
||||
await _repository.UpdateAsync(entity);
|
||||
}
|
||||
//不存在插入
|
||||
else
|
||||
{
|
||||
var usage = new UsageStatisticsAggregateRoot(userId, modelId);
|
||||
usage.AddOnceChat(inputTokenCount, outputTokenCount);
|
||||
await _repository.InsertAsync(usage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class LazyServiceProvider
|
||||
{
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" Version="2.2.0-beta.4" />
|
||||
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="$(AbpVersion)" />
|
||||
|
||||
<PackageReference Include="Volo.Abp.DistributedLocking" Version="$(AbpVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -11,8 +11,8 @@ VITE_WEB_ENV = 'development'
|
||||
VITE_WEB_BASE_API = '/dev-api'
|
||||
|
||||
# 本地接口
|
||||
# VITE_API_URL = http://localhost:19001/api/app
|
||||
VITE_API_URL=http://ccnetcore.com:19001/api/app
|
||||
VITE_API_URL = http://localhost:19001/api/app
|
||||
#VITE_API_URL=http://ccnetcore.com:19001/api/app
|
||||
|
||||
# SSO单点登录url
|
||||
# SSO_SEVER_URL='http://localhost:18001'
|
||||
|
||||
Reference in New Issue
Block a user