feat: 完成用量统计功能模块

This commit is contained in:
ccnetcore
2025-06-27 22:13:26 +08:00
parent 96e275efa6
commit 01a3c81359
20 changed files with 281 additions and 98 deletions

View File

@@ -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 class MessageInputDto
{ {
public string Content { get; set; } public string Content { get; set; }
public string Role { get; set; } public string Role { get; set; }
public decimal DeductCost { get; set; }
public decimal TotalTokens { get; set; }
public string ModelId { get; set; } public string ModelId { get; set; }
public string Remark { get; set; } public string? Remark { get; set; }
public TokenUsage? TokenUsage { get; set; }
} }

View File

@@ -12,7 +12,9 @@ using Volo.Abp.Application.Services;
using Volo.Abp.Users; using Volo.Abp.Users;
using Yi.Framework.AiHub.Application.Contracts.Dtos; using Yi.Framework.AiHub.Application.Contracts.Dtos;
using Yi.Framework.AiHub.Domain.Entities; using Yi.Framework.AiHub.Domain.Entities;
using Yi.Framework.AiHub.Domain.Entities.Model;
using Yi.Framework.AiHub.Domain.Managers; using Yi.Framework.AiHub.Domain.Managers;
using Yi.Framework.AiHub.Domain.Shared.Dtos;
using Yi.Framework.Rbac.Application.Contracts.IServices; using Yi.Framework.Rbac.Application.Contracts.IServices;
using Yi.Framework.Rbac.Domain.Shared.Dtos; using Yi.Framework.Rbac.Domain.Shared.Dtos;
using Yi.Framework.SqlSugarCore.Abstractions; using Yi.Framework.SqlSugarCore.Abstractions;
@@ -28,15 +30,17 @@ public class AiChatService : ApplicationService
private readonly AiMessageManager _aiMessageManager; private readonly AiMessageManager _aiMessageManager;
private readonly ISqlSugarRepository<AiModelEntity> _aiModelRepository; private readonly ISqlSugarRepository<AiModelEntity> _aiModelRepository;
private readonly AiBlacklistManager _aiBlacklistManager; private readonly AiBlacklistManager _aiBlacklistManager;
private readonly UsageStatisticsManager _usageStatisticsManager;
public AiChatService(IHttpContextAccessor httpContextAccessor, public AiChatService(IHttpContextAccessor httpContextAccessor,
AiMessageManager aiMessageManager, AiBlacklistManager aiBlacklistManager, AiMessageManager aiMessageManager, AiBlacklistManager aiBlacklistManager,
ISqlSugarRepository<AiModelEntity> aiModelRepository) ISqlSugarRepository<AiModelEntity> aiModelRepository, UsageStatisticsManager usageStatisticsManager)
{ {
this._httpContextAccessor = httpContextAccessor; this._httpContextAccessor = httpContextAccessor;
_aiMessageManager = aiMessageManager; _aiMessageManager = aiMessageManager;
_aiBlacklistManager = aiBlacklistManager; _aiBlacklistManager = aiBlacklistManager;
_aiModelRepository = aiModelRepository; _aiModelRepository = aiModelRepository;
_usageStatisticsManager = usageStatisticsManager;
} }
@@ -60,21 +64,21 @@ public class AiChatService : ApplicationService
public async Task<List<ModelGetListOutput>> GetModelAsync() public async Task<List<ModelGetListOutput>> GetModelAsync()
{ {
var output = await _aiModelRepository._DbQueryable var output = await _aiModelRepository._DbQueryable
.OrderByDescending(x=>x.OrderNum) .OrderByDescending(x => x.OrderNum)
.Select(x => new ModelGetListOutput .Select(x => new ModelGetListOutput
{ {
Id = x.Id, Id = x.Id,
Category = "chat", Category = "chat",
ModelName = x.Name, ModelName = x.Name,
ModelDescribe = x.Description, ModelDescribe = x.Description,
ModelPrice = 0, ModelPrice = 0,
ModelType = "1", ModelType = "1",
ModelShow = "0", ModelShow = "0",
SystemPrompt = null, SystemPrompt = null,
ApiHost = null, ApiHost = null,
ApiKey = null, ApiKey = null,
Remark = x.Description Remark = x.Description
}).ToListAsync(); }).ToListAsync();
return output; return output;
} }
@@ -128,13 +132,15 @@ public class AiChatService : ApplicationService
var gateWay = LazyServiceProvider.GetRequiredService<AiGateWayManager>(); var gateWay = LazyServiceProvider.GetRequiredService<AiGateWayManager>();
var completeChatResponse = gateWay.CompleteChatAsync(input.Model, history, cancellationToken); var completeChatResponse = gateWay.CompleteChatAsync(input.Model, history, cancellationToken);
var tokenUsage = new TokenUsage();
await using var writer = new StreamWriter(response.Body, Encoding.UTF8, leaveOpen: true); await using var writer = new StreamWriter(response.Body, Encoding.UTF8, leaveOpen: true);
//缓存队列算法 //缓存队列算法
// 创建一个队列来缓存消息 // 创建一个队列来缓存消息
var messageQueue = new ConcurrentQueue<string>(); var messageQueue = new ConcurrentQueue<string>();
StringBuilder backupSystemContent = new StringBuilder();
// 设置输出速率例如每50毫秒输出一次 // 设置输出速率例如每50毫秒输出一次
var outputInterval = TimeSpan.FromMilliseconds(100); var outputInterval = TimeSpan.FromMilliseconds(100);
// 标记是否完成接收 // 标记是否完成接收
@@ -161,33 +167,49 @@ public class AiChatService : ApplicationService
await foreach (var data in completeChatResponse) 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 var message = JsonConvert.SerializeObject(model, new JsonSerializerSettings
{ {
ContractResolver = new CamelCasePropertyNamesContractResolver() ContractResolver = new CamelCasePropertyNamesContractResolver()
}); });
backupSystemContent.Append(data.Content);
// 将消息加入队列而不是直接写入 // 将消息加入队列而不是直接写入
messageQueue.Enqueue($"data: {message}\n"); messageQueue.Enqueue($"data: {message}\n");
} }
// 标记完成并发送结束标记
isComplete = true;
//断开连接 //断开连接
messageQueue.Enqueue("data: done\n"); messageQueue.Enqueue("data: done\n");
// 标记完成并发送结束标记
isComplete = true;
await outputTask; await outputTask;
if (CurrentUser.IsAuthenticated && input.SessionId.HasValue) if (CurrentUser.IsAuthenticated && input.SessionId.HasValue)
{ {
await _aiMessageManager.CreateMessageAsync(CurrentUser.GetId(), input.SessionId.Value, new MessageInputDto await _aiMessageManager.CreateUserMessageAsync(CurrentUser.GetId(), input.SessionId.Value,
{ new MessageInputDto
Content = input.Messages.LastOrDefault().Content, {
Role = input.Messages.LastOrDefault().Role, Content = input.Messages.LastOrDefault()
DeductCost = 0, .Content,
TotalTokens = 0, ModelId = input.Model,
ModelId = input.Model, TokenUsage = tokenUsage,
Remark = null });
});
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 = "", SystemFingerprint = "",
Usage = new Usage Usage = new Usage
{ {
PromptTokens = 75, PromptTokens = 0,
CompletionTokens = 25, CompletionTokens = 0,
TotalTokens = 100, TotalTokens = 0,
PromptTokensDetails = new() PromptTokensDetails = new()
{ {
AudioTokens = 0, AudioTokens = 0,

View File

@@ -7,6 +7,7 @@ using Volo.Abp.Application.Services;
using Volo.Abp.Users; using Volo.Abp.Users;
using Yi.Framework.AiHub.Application.Contracts.Dtos; using Yi.Framework.AiHub.Application.Contracts.Dtos;
using Yi.Framework.AiHub.Domain.Entities; using Yi.Framework.AiHub.Domain.Entities;
using Yi.Framework.AiHub.Domain.Entities.Chat;
using Yi.Framework.SqlSugarCore.Abstractions; using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.AiHub.Application.Services; namespace Yi.Framework.AiHub.Application.Services;
@@ -34,7 +35,7 @@ public class MessageService : ApplicationService
var entities = await _repository._DbQueryable var entities = await _repository._DbQueryable
.Where(x => x.SessionId == input.SessionId) .Where(x => x.SessionId == input.SessionId)
.Where(x=>x.UserId == userId) .Where(x=>x.UserId == userId)
.OrderByDescending(x => x.Id) .OrderBy(x => x.Id)
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total); .ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
return new PagedResultDto<MessageDto>(total, entities.Adapt<List<MessageDto>>()); return new PagedResultDto<MessageDto>(total, entities.Adapt<List<MessageDto>>());
} }

View File

@@ -8,6 +8,7 @@ using Volo.Abp.Domain.Repositories;
using Volo.Abp.Users; using Volo.Abp.Users;
using Yi.Framework.AiHub.Application.Contracts.Dtos; using Yi.Framework.AiHub.Application.Contracts.Dtos;
using Yi.Framework.AiHub.Domain.Entities; using Yi.Framework.AiHub.Domain.Entities;
using Yi.Framework.AiHub.Domain.Entities.Chat;
using Yi.Framework.SqlSugarCore.Abstractions; using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.AiHub.Application.Services; namespace Yi.Framework.AiHub.Application.Services;

View File

@@ -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; }
}

View File

@@ -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; }
}

View File

@@ -5,6 +5,6 @@ namespace Yi.Framework.AiHub.Domain.AiChat;
public interface IChatService public interface IChatService
{ {
public IAsyncEnumerable<string> CompleteChatAsync(AiModelDescribe aiModelDescribe, List<ChatMessage> messages, public IAsyncEnumerable<CompleteChatResponse> CompleteChatAsync(AiModelDescribe aiModelDescribe, List<ChatMessage> messages,
CancellationToken cancellationToken); CancellationToken cancellationToken);
} }

View File

@@ -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) [EnumeratorCancellation] CancellationToken cancellationToken)
{ {
var endpoint = new Uri(aiModelDescribe.Endpoint); var endpoint = new Uri(aiModelDescribe.Endpoint);
@@ -32,9 +33,28 @@ public class AzureChatService : IChatService
await foreach (StreamingChatCompletionUpdate update in response) 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) foreach (ChatMessageContentPart updatePart in update.ContentUpdate)
{ {
yield return updatePart.Text; result.Content = updatePart.Text;
yield return result;
}
if (isFinish)
{
yield return result;
} }
} }
} }

View File

@@ -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) [EnumeratorCancellation] CancellationToken cancellationToken)
{ {
// 设置API URL // 设置API URL
@@ -61,51 +62,65 @@ public class AzureRestChatService : IChatService
var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken); var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken);
// 从流中读取数据并输出到控制台 // 从流中读取数据并输出到控制台
using var streamReader = new StreamReader(responseStream); using var streamReader = new StreamReader(responseStream);
string line; while (await streamReader.ReadLineAsync(cancellationToken) is { } line)
while ((line = await streamReader.ReadLineAsync(cancellationToken)) != null)
{ {
var result = GetContent(line); var result = new CompleteChatResponse();
if (result is not null) 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)) if (string.IsNullOrWhiteSpace(line))
return null; return null;
string prefix = "data: "; string prefix = "data: ";
line = line.Substring(prefix.Length); 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 result = new TokenUsage()
var jsonObj = JObject.Parse(line); {
var content = jsonObj["choices"][0]["delta"]["content"].ToString(); OutputTokenCount = usage["completion_tokens"].ToObject<int>(),
return content; InputTokenCount = usage["prompt_tokens"].ToObject<int>(),
// // 判断choices是否存在且是数组并且有元素 TotalTokenCount = usage["total_tokens"].ToObject<int>()
// if (jsonObj.TryGetValue("choices", out var choicesToken) && choicesToken is JArray choicesArray && };
// choicesArray.Count > 0)
// { return result;
// 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;
} }
return null;
} }
} }

View File

@@ -1,7 +1,10 @@
using SqlSugar; using Mapster;
using SqlSugar;
using Volo.Abp.Domain.Entities.Auditing; 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")] [SugarTable("Ai_Message")]
[SugarIndex($"index_{{table}}_{nameof(UserId)}_{nameof(SessionId)}", [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; UserId = userId;
SessionId = sessionId; SessionId = sessionId;
Content = content; Content = content;
Role = role; Role = role;
ModelId = modelId; ModelId = modelId;
if (tokenUsage is not null)
{
this.TokenUsage = tokenUsage.Adapt<TokenUsageValueObject>();
}
} }
public Guid UserId { get; set; } public Guid UserId { get; set; }
public Guid SessionId { get; set; } public Guid SessionId { get; set; }
[SugarColumn(ColumnDataType = StaticConfig.CodeFirst_BigString)]
public string Content { get; set; } public string Content { get; set; }
public string Role { get; set; } public string Role { get; set; }
public decimal DeductCost { get; set; } public decimal DeductCost { get; set; }
public decimal TotalTokens { get; set; } public decimal TotalTokens { get; set; }
public string ModelId { 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; }
} }

View File

@@ -1,7 +1,7 @@
using SqlSugar; using SqlSugar;
using Volo.Abp.Domain.Entities.Auditing; using Volo.Abp.Domain.Entities.Auditing;
namespace Yi.Framework.AiHub.Domain.Entities; namespace Yi.Framework.AiHub.Domain.Entities.Chat;
[SugarTable("Ai_Session")] [SugarTable("Ai_Session")]
[SugarIndex($"index_{{table}}_{nameof(UserId)}",$"{nameof(UserId)}", OrderByType.Asc)] [SugarIndex($"index_{{table}}_{nameof(UserId)}",$"{nameof(UserId)}", OrderByType.Asc)]

View File

@@ -2,7 +2,7 @@
using Volo.Abp.Domain.Entities.Auditing; using Volo.Abp.Domain.Entities.Auditing;
using Yi.Framework.Core.Data; using Yi.Framework.Core.Data;
namespace Yi.Framework.AiHub.Domain.Entities; namespace Yi.Framework.AiHub.Domain.Entities.Model;
/// <summary> /// <summary>
/// ai应用 /// ai应用

View File

@@ -1,9 +1,8 @@
using SqlSugar; using SqlSugar;
using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Entities.Auditing;
using Yi.Framework.Core.Data; using Yi.Framework.Core.Data;
namespace Yi.Framework.AiHub.Domain.Entities; namespace Yi.Framework.AiHub.Domain.Entities.Model;
/// <summary> /// <summary>
/// ai模型定义 /// ai模型定义

View File

@@ -9,6 +9,16 @@ namespace Yi.Framework.AiHub.Domain.Entities;
[SugarTable("Ai_UsageStatistics")] [SugarTable("Ai_UsageStatistics")]
public class UsageStatisticsAggregateRoot : FullAuditedAggregateRoot<Guid> public class UsageStatisticsAggregateRoot : FullAuditedAggregateRoot<Guid>
{ {
public UsageStatisticsAggregateRoot()
{
}
public UsageStatisticsAggregateRoot(Guid userId, string modelId)
{
UserId = userId;
ModelId = modelId;
}
/// <summary> /// <summary>
/// 用户id /// 用户id
/// </summary> /// </summary>
@@ -19,18 +29,34 @@ public class UsageStatisticsAggregateRoot : FullAuditedAggregateRoot<Guid>
/// </summary> /// </summary>
public string ModelId { get; set; } public string ModelId { get; set; }
/// <summary>
/// 输入使用token使用
/// </summary>
public decimal InputTokens { get; set; }
/// <summary>
/// 输出使用token使用
/// </summary>
public decimal OutputTokens { get; set; }
/// <summary> /// <summary>
/// 对话次数 /// 对话次数
/// </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);
}
} }

View File

@@ -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; }
}

View File

@@ -4,6 +4,7 @@ using OpenAI.Chat;
using Volo.Abp.Domain.Services; using Volo.Abp.Domain.Services;
using Yi.Framework.AiHub.Domain.AiChat; using Yi.Framework.AiHub.Domain.AiChat;
using Yi.Framework.AiHub.Domain.Entities; using Yi.Framework.AiHub.Domain.Entities;
using Yi.Framework.AiHub.Domain.Entities.Model;
using Yi.Framework.AiHub.Domain.Shared.Dtos; using Yi.Framework.AiHub.Domain.Shared.Dtos;
using Yi.Framework.SqlSugarCore.Abstractions; using Yi.Framework.SqlSugarCore.Abstractions;
@@ -57,7 +58,7 @@ public class AiGateWayManager : DomainService
/// <param name="messages"></param> /// <param name="messages"></param>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
/// <returns></returns> /// <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) [EnumeratorCancellation] CancellationToken cancellationToken)
{ {
var modelDescribe = await GetModelAsync(modelId); var modelDescribe = await GetModelAsync(modelId);

View File

@@ -2,6 +2,7 @@
using Volo.Abp.Users; using Volo.Abp.Users;
using Yi.Framework.AiHub.Application.Contracts.Dtos; using Yi.Framework.AiHub.Application.Contracts.Dtos;
using Yi.Framework.AiHub.Domain.Entities; using Yi.Framework.AiHub.Domain.Entities;
using Yi.Framework.AiHub.Domain.Entities.Chat;
using Yi.Framework.SqlSugarCore.Abstractions; using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.AiHub.Domain.Managers; namespace Yi.Framework.AiHub.Domain.Managers;
@@ -16,15 +17,30 @@ public class AiMessageManager : DomainService
} }
/// <summary> /// <summary>
/// 创建消息 /// 创建系统消息
/// </summary> /// </summary>
/// <param name="sessionId"></param> /// <param name="sessionId"></param>
/// <param name="userId"></param> /// <param name="userId"></param>
/// <param name="input"></param> /// <param name="input"></param>
/// <returns></returns> /// <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); await _repository.InsertAsync(message);
} }
} }

View File

@@ -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
{
}

View File

@@ -3,7 +3,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" Version="2.2.0-beta.4" /> <PackageReference Include="Azure.AI.OpenAI" Version="2.2.0-beta.4" />
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="$(AbpVersion)" /> <PackageReference Include="Volo.Abp.Ddd.Domain" Version="$(AbpVersion)" />
<PackageReference Include="Volo.Abp.DistributedLocking" Version="$(AbpVersion)" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -11,8 +11,8 @@ VITE_WEB_ENV = 'development'
VITE_WEB_BASE_API = '/dev-api' VITE_WEB_BASE_API = '/dev-api'
# 本地接口 # 本地接口
# VITE_API_URL = http://localhost:19001/api/app VITE_API_URL = http://localhost:19001/api/app
VITE_API_URL=http://ccnetcore.com:19001/api/app #VITE_API_URL=http://ccnetcore.com:19001/api/app
# SSO单点登录url # SSO单点登录url
# SSO_SEVER_URL='http://localhost:18001' # SSO_SEVER_URL='http://localhost:18001'