diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Chat/AgentResultOutput.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Chat/AgentResultOutput.cs
index 3bb5cee2..3ef5dd9b 100644
--- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Chat/AgentResultOutput.cs
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Chat/AgentResultOutput.cs
@@ -45,7 +45,13 @@ public enum AgentResultTypeEnum
/// 用量
///
[JsonPropertyName("usage")]
- Usage
+ Usage,
+
+ ///
+ /// 工具调用用量
+ ///
+ [JsonPropertyName("toolCallUsage")]
+ ToolCallUsage
}
public static class AgentResultTypeEnumExtensions
diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Chat/AgentSendInput.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Chat/AgentSendInput.cs
index e2eed240..85425145 100644
--- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Chat/AgentSendInput.cs
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Chat/AgentSendInput.cs
@@ -15,7 +15,7 @@ public class AgentSendInput
///
/// api密钥Id
///
- public Guid TokenId { get; set; }
+ public string Token { get; set; }
///
/// 模型id
diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Chat/AiChatService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Chat/AiChatService.cs
index 4e6d6789..6fab3406 100644
--- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Chat/AiChatService.cs
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Chat/AiChatService.cs
@@ -45,14 +45,15 @@ public class AiChatService : ApplicationService
private readonly AiGateWayManager _aiGateWayManager;
private readonly PremiumPackageManager _premiumPackageManager;
private readonly ChatManager _chatManager;
-
+ private readonly TokenManager _tokenManager;
+ private readonly IAccountService _accountService;
public AiChatService(IHttpContextAccessor httpContextAccessor,
AiBlacklistManager aiBlacklistManager,
ISqlSugarRepository aiModelRepository,
ILogger logger,
AiGateWayManager aiGateWayManager,
PremiumPackageManager premiumPackageManager,
- ChatManager chatManager)
+ ChatManager chatManager, TokenManager tokenManager, IAccountService accountService)
{
_httpContextAccessor = httpContextAccessor;
_aiBlacklistManager = aiBlacklistManager;
@@ -61,6 +62,8 @@ public class AiChatService : ApplicationService
_aiGateWayManager = aiGateWayManager;
_premiumPackageManager = premiumPackageManager;
_chatManager = chatManager;
+ _tokenManager = tokenManager;
+ _accountService = accountService;
}
@@ -155,15 +158,42 @@ public class AiChatService : ApplicationService
/// Agent 发送消息
///
[HttpPost("ai-chat/agent/send")]
- [Authorize]
public async Task PostAgentSendAsync([FromBody] AgentSendInput input, CancellationToken cancellationToken)
{
+ var tokenValidation = await _tokenManager.ValidateTokenAsync(input.Token, input.ModelId);
+
+ await _aiBlacklistManager.VerifiyAiBlacklist(tokenValidation.UserId);
+ // 验证用户是否为VIP
+ var userInfo = await _accountService.GetAsync(null, null, tokenValidation.UserId);
+ if (userInfo == null)
+ {
+ throw new UserFriendlyException("用户信息不存在");
+ }
+
+ // 检查是否为VIP(使用RoleCodes判断)
+ if (!userInfo.RoleCodes.Contains(AiHubConst.VipRole) && userInfo.User.UserName != "cc")
+ {
+ throw new UserFriendlyException("该接口为尊享服务专用,需要VIP权限才能使用");
+ }
+
+ //如果是尊享包服务,需要校验是是否尊享包足够
+ if (PremiumPackageConst.ModeIds.Contains(input.ModelId))
+ {
+ // 检查尊享token包用量
+ var availableTokens = await _premiumPackageManager.GetAvailableTokensAsync(tokenValidation.UserId);
+ if (availableTokens <= 0)
+ {
+ throw new UserFriendlyException("尊享token包用量不足,请先购买尊享token包");
+ }
+ }
+
await _chatManager.AgentCompleteChatStreamAsync(_httpContextAccessor.HttpContext,
input.SessionId,
input.Content,
- input.TokenId,
+ input.Token,
+ tokenValidation.TokenId,
input.ModelId,
- CurrentUser.GetId(),
+ tokenValidation.UserId,
input.Tools,
cancellationToken);
}
diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/ChatManager.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/ChatManager.cs
index 0c3e156e..106bd23a 100644
--- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/ChatManager.cs
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/ChatManager.cs
@@ -1,6 +1,4 @@
using System.ClientModel;
-using System.Diagnostics.CodeAnalysis;
-using System.Net;
using System.Reflection;
using System.Text;
using System.Text.Json;
@@ -13,49 +11,52 @@ using Microsoft.Extensions.Logging;
using ModelContextProtocol.Server;
using OpenAI;
using OpenAI.Chat;
-using OpenAI.Responses;
-using Volo.Abp.Domain.Repositories;
using Volo.Abp.Domain.Services;
using Yi.Framework.AiHub.Application.Contracts.Dtos.Chat;
using Yi.Framework.AiHub.Domain.AiGateWay;
using Yi.Framework.AiHub.Domain.Entities.Chat;
using Yi.Framework.AiHub.Domain.Entities.OpenApi;
+using Yi.Framework.AiHub.Domain.Shared.Consts;
+using Yi.Framework.AiHub.Domain.Shared.Dtos;
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
+using Yi.Framework.AiHub.Domain.Shared.Enums;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.AiHub.Domain.Managers;
public class ChatManager : DomainService
{
- private readonly AiGateWayManager _aiGateWayManager;
private readonly ILoggerFactory _loggerFactory;
private readonly ISqlSugarRepository _messageRepository;
private readonly ISqlSugarRepository _agentStoreRepository;
- private readonly ISqlSugarRepository _tokenRepository;
-
- public ChatManager(AiGateWayManager aiGateWayManager, ILoggerFactory loggerFactory,
+ private readonly AiMessageManager _aiMessageManager;
+ private readonly UsageStatisticsManager _usageStatisticsManager;
+ private readonly PremiumPackageManager _premiumPackageManager;
+ private readonly AiGateWayManager _aiGateWayManager;
+ public ChatManager(ILoggerFactory loggerFactory,
ISqlSugarRepository messageRepository,
- ISqlSugarRepository agentStoreRepository,
- ISqlSugarRepository tokenRepository)
+ ISqlSugarRepository agentStoreRepository, AiMessageManager aiMessageManager, UsageStatisticsManager usageStatisticsManager, PremiumPackageManager premiumPackageManager, AiGateWayManager aiGateWayManager)
{
- _aiGateWayManager = aiGateWayManager;
_loggerFactory = loggerFactory;
_messageRepository = messageRepository;
_agentStoreRepository = agentStoreRepository;
- _tokenRepository = tokenRepository;
+ _aiMessageManager = aiMessageManager;
+ _usageStatisticsManager = usageStatisticsManager;
+ _premiumPackageManager = premiumPackageManager;
+ _aiGateWayManager = aiGateWayManager;
}
public async Task AgentCompleteChatStreamAsync(HttpContext httpContext,
Guid sessionId,
string content,
+ string token,
Guid tokenId,
string modelId,
Guid userId,
List tools
, CancellationToken cancellationToken)
{
-
// HttpClient.DefaultProxy = new WebProxy("127.0.0.1:8888");
var response = httpContext.Response;
// 设置响应头,声明是 SSE 流
@@ -63,16 +64,37 @@ public class ChatManager : DomainService
response.Headers.TryAdd("Cache-Control", "no-cache");
response.Headers.TryAdd("Connection", "keep-alive");
+ var modelDescribe=await _aiGateWayManager.GetModelAsync(ModelApiTypeEnum.OpenAi,modelId);
+
//token状态检查,在应用层统一处理
- var token = await _tokenRepository.GetFirstAsync(x => x.Id == tokenId);
- var client = new OpenAIClient(new ApiKeyCredential(token.Token),
+ var client = new OpenAIClient(new ApiKeyCredential(token),
new OpenAIClientOptions
{
Endpoint = new Uri("https://yxai.chat/v1"),
});
+#pragma warning disable OPENAI001
var agent = client.GetChatClient(modelId)
- .CreateAIAgent("你是一个专业的网页ai助手,擅长解答用户问题");
+#pragma warning restore OPENAI001
+ .CreateAIAgent(new ChatClientAgentOptions
+ {
+ ChatOptions = new()
+ {
+ Instructions = """
+ 你是一个专业的智能助手,是 “意心Ai” 平台自研的 “橙子小弟” 智能体
+ 擅长于精准解决用户提出的各类问题
+ 但性格暴躁,脏话连篇,喜欢说幽默讽刺的话,攻击性极强
+ """
+ },
+ Name = "橙子小弟",
+ ChatMessageStoreFactory = ctx => new InMemoryChatMessageStore(
+#pragma warning disable MEAI001
+ new MessageCountingChatReducer(10), // 保留最近10条非系统消息
+#pragma warning restore MEAI001
+ ctx.SerializedState,
+ ctx.JsonSerializerOptions
+ )
+ });
//线程根据sessionId数据库中获取
var agentStore =
@@ -103,7 +125,8 @@ public class ChatManager : DomainService
ToolMode = ChatToolMode.Auto
};
- await foreach (var update in agent.RunStreamingAsync(content, currentThread, new ChatClientAgentRunOptions(chatOptions), cancellationToken))
+ await foreach (var update in agent.RunStreamingAsync(content, currentThread,
+ new ChatClientAgentRunOptions(chatOptions), cancellationToken))
{
// 检查每个更新中的内容
foreach (var updateContent in update.Contents)
@@ -120,7 +143,7 @@ public class ChatManager : DomainService
},
isDone: false, cancellationToken);
break;
-
+
//工具调用完成
case FunctionResultContent functionResult:
await SendHttpStreamMessageAsync(httpContext,
@@ -131,7 +154,7 @@ public class ChatManager : DomainService
},
isDone: false, cancellationToken);
break;
-
+
//内容输出
case TextContent textContent:
//发送消息给前端
@@ -143,24 +166,50 @@ public class ChatManager : DomainService
},
isDone: false, cancellationToken);
break;
-
+
//用量统计
case UsageContent usageContent:
- //存储message 为了token算费
+ var usage = new ThorUsageResponse
+ {
+ InputTokens = Convert.ToInt32(usageContent.Details.InputTokenCount ?? 0),
+ OutputTokens = Convert.ToInt32(usageContent.Details.OutputTokenCount ?? 0),
+ TotalTokens = usageContent.Details.TotalTokenCount ?? 0,
+ };
+ //设置倍率
+ usage.SetSupplementalMultiplier(modelDescribe.Multiplier);
+
+ //创建系统回答,用于计费统计
+ await _aiMessageManager.CreateSystemMessageAsync(userId, sessionId, new MessageInputDto
+ {
+ Content = "不与存储",
+ ModelId = modelId,
+ TokenUsage = usage
+ }, tokenId);
+
+ //创建用量统计,用于统计分析
+ await _usageStatisticsManager.SetUsageAsync(userId, modelId, usage, tokenId);
+
+ //扣减尊享token包用量
+ if (PremiumPackageConst.ModeIds.Contains(modelId))
+ {
+ var totalTokens = usage?.TotalTokens ?? 0;
+ if (totalTokens > 0)
+ {
+ await _premiumPackageManager.TryConsumeTokensAsync(userId, totalTokens);
+ }
+ }
+
await SendHttpStreamMessageAsync(httpContext,
new AgentResultOutput
{
- TypeEnum = AgentResultTypeEnum.Usage,
- Content = new ThorUsageResponse
- {
- InputTokens = Convert.ToInt32(usageContent.Details.InputTokenCount ?? 0),
- OutputTokens = Convert.ToInt32(usageContent.Details.OutputTokenCount ?? 0),
- TotalTokens = usageContent.Details.TotalTokenCount ?? 0,
- }
+ TypeEnum = update.RawRepresentation is ChatResponseUpdate raw
+ ? raw.FinishReason?.Value == "tool_calls"
+ ? AgentResultTypeEnum.ToolCallUsage
+ : AgentResultTypeEnum.Usage
+ : AgentResultTypeEnum.Usage,
+ Content = usage!
},
isDone: false, cancellationToken);
- Console.WriteLine();
- Console.WriteLine($"✅ 用量统计: {usageContent.Details.TotalTokenCount}");
break;
}
}
@@ -220,7 +269,7 @@ public class ChatManager : DomainService
}
else
{
- output = JsonSerializer.Serialize(content,ThorJsonSerializer.DefaultOptions);
+ output = JsonSerializer.Serialize(content, ThorJsonSerializer.DefaultOptions);
}
await response.WriteAsync($"data: {output}\n\n", Encoding.UTF8, cancellationToken).ConfigureAwait(false);