feat: 完成agent接口功能
This commit is contained in:
@@ -45,7 +45,13 @@ public enum AgentResultTypeEnum
|
|||||||
/// 用量
|
/// 用量
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonPropertyName("usage")]
|
[JsonPropertyName("usage")]
|
||||||
Usage
|
Usage,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 工具调用用量
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("toolCallUsage")]
|
||||||
|
ToolCallUsage
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class AgentResultTypeEnumExtensions
|
public static class AgentResultTypeEnumExtensions
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public class AgentSendInput
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// api密钥Id
|
/// api密钥Id
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Guid TokenId { get; set; }
|
public string Token { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 模型id
|
/// 模型id
|
||||||
|
|||||||
@@ -45,14 +45,15 @@ public class AiChatService : ApplicationService
|
|||||||
private readonly AiGateWayManager _aiGateWayManager;
|
private readonly AiGateWayManager _aiGateWayManager;
|
||||||
private readonly PremiumPackageManager _premiumPackageManager;
|
private readonly PremiumPackageManager _premiumPackageManager;
|
||||||
private readonly ChatManager _chatManager;
|
private readonly ChatManager _chatManager;
|
||||||
|
private readonly TokenManager _tokenManager;
|
||||||
|
private readonly IAccountService _accountService;
|
||||||
public AiChatService(IHttpContextAccessor httpContextAccessor,
|
public AiChatService(IHttpContextAccessor httpContextAccessor,
|
||||||
AiBlacklistManager aiBlacklistManager,
|
AiBlacklistManager aiBlacklistManager,
|
||||||
ISqlSugarRepository<AiModelEntity> aiModelRepository,
|
ISqlSugarRepository<AiModelEntity> aiModelRepository,
|
||||||
ILogger<AiChatService> logger,
|
ILogger<AiChatService> logger,
|
||||||
AiGateWayManager aiGateWayManager,
|
AiGateWayManager aiGateWayManager,
|
||||||
PremiumPackageManager premiumPackageManager,
|
PremiumPackageManager premiumPackageManager,
|
||||||
ChatManager chatManager)
|
ChatManager chatManager, TokenManager tokenManager, IAccountService accountService)
|
||||||
{
|
{
|
||||||
_httpContextAccessor = httpContextAccessor;
|
_httpContextAccessor = httpContextAccessor;
|
||||||
_aiBlacklistManager = aiBlacklistManager;
|
_aiBlacklistManager = aiBlacklistManager;
|
||||||
@@ -61,6 +62,8 @@ public class AiChatService : ApplicationService
|
|||||||
_aiGateWayManager = aiGateWayManager;
|
_aiGateWayManager = aiGateWayManager;
|
||||||
_premiumPackageManager = premiumPackageManager;
|
_premiumPackageManager = premiumPackageManager;
|
||||||
_chatManager = chatManager;
|
_chatManager = chatManager;
|
||||||
|
_tokenManager = tokenManager;
|
||||||
|
_accountService = accountService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -155,15 +158,42 @@ public class AiChatService : ApplicationService
|
|||||||
/// Agent 发送消息
|
/// Agent 发送消息
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HttpPost("ai-chat/agent/send")]
|
[HttpPost("ai-chat/agent/send")]
|
||||||
[Authorize]
|
|
||||||
public async Task PostAgentSendAsync([FromBody] AgentSendInput input, CancellationToken cancellationToken)
|
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,
|
await _chatManager.AgentCompleteChatStreamAsync(_httpContextAccessor.HttpContext,
|
||||||
input.SessionId,
|
input.SessionId,
|
||||||
input.Content,
|
input.Content,
|
||||||
input.TokenId,
|
input.Token,
|
||||||
|
tokenValidation.TokenId,
|
||||||
input.ModelId,
|
input.ModelId,
|
||||||
CurrentUser.GetId(),
|
tokenValidation.UserId,
|
||||||
input.Tools,
|
input.Tools,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using System.ClientModel;
|
using System.ClientModel;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Net;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
@@ -13,49 +11,52 @@ using Microsoft.Extensions.Logging;
|
|||||||
using ModelContextProtocol.Server;
|
using ModelContextProtocol.Server;
|
||||||
using OpenAI;
|
using OpenAI;
|
||||||
using OpenAI.Chat;
|
using OpenAI.Chat;
|
||||||
using OpenAI.Responses;
|
|
||||||
using Volo.Abp.Domain.Repositories;
|
|
||||||
using Volo.Abp.Domain.Services;
|
using Volo.Abp.Domain.Services;
|
||||||
using Yi.Framework.AiHub.Application.Contracts.Dtos.Chat;
|
using Yi.Framework.AiHub.Application.Contracts.Dtos.Chat;
|
||||||
using Yi.Framework.AiHub.Domain.AiGateWay;
|
using Yi.Framework.AiHub.Domain.AiGateWay;
|
||||||
using Yi.Framework.AiHub.Domain.Entities.Chat;
|
using Yi.Framework.AiHub.Domain.Entities.Chat;
|
||||||
using Yi.Framework.AiHub.Domain.Entities.OpenApi;
|
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.Dtos.OpenAi;
|
||||||
|
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||||
|
|
||||||
namespace Yi.Framework.AiHub.Domain.Managers;
|
namespace Yi.Framework.AiHub.Domain.Managers;
|
||||||
|
|
||||||
public class ChatManager : DomainService
|
public class ChatManager : DomainService
|
||||||
{
|
{
|
||||||
private readonly AiGateWayManager _aiGateWayManager;
|
|
||||||
private readonly ILoggerFactory _loggerFactory;
|
private readonly ILoggerFactory _loggerFactory;
|
||||||
private readonly ISqlSugarRepository<MessageAggregateRoot> _messageRepository;
|
private readonly ISqlSugarRepository<MessageAggregateRoot> _messageRepository;
|
||||||
private readonly ISqlSugarRepository<AgentStoreAggregateRoot> _agentStoreRepository;
|
private readonly ISqlSugarRepository<AgentStoreAggregateRoot> _agentStoreRepository;
|
||||||
private readonly ISqlSugarRepository<TokenAggregateRoot> _tokenRepository;
|
private readonly AiMessageManager _aiMessageManager;
|
||||||
|
private readonly UsageStatisticsManager _usageStatisticsManager;
|
||||||
public ChatManager(AiGateWayManager aiGateWayManager, ILoggerFactory loggerFactory,
|
private readonly PremiumPackageManager _premiumPackageManager;
|
||||||
|
private readonly AiGateWayManager _aiGateWayManager;
|
||||||
|
public ChatManager(ILoggerFactory loggerFactory,
|
||||||
ISqlSugarRepository<MessageAggregateRoot> messageRepository,
|
ISqlSugarRepository<MessageAggregateRoot> messageRepository,
|
||||||
ISqlSugarRepository<AgentStoreAggregateRoot> agentStoreRepository,
|
ISqlSugarRepository<AgentStoreAggregateRoot> agentStoreRepository, AiMessageManager aiMessageManager, UsageStatisticsManager usageStatisticsManager, PremiumPackageManager premiumPackageManager, AiGateWayManager aiGateWayManager)
|
||||||
ISqlSugarRepository<TokenAggregateRoot> tokenRepository)
|
|
||||||
{
|
{
|
||||||
_aiGateWayManager = aiGateWayManager;
|
|
||||||
_loggerFactory = loggerFactory;
|
_loggerFactory = loggerFactory;
|
||||||
_messageRepository = messageRepository;
|
_messageRepository = messageRepository;
|
||||||
_agentStoreRepository = agentStoreRepository;
|
_agentStoreRepository = agentStoreRepository;
|
||||||
_tokenRepository = tokenRepository;
|
_aiMessageManager = aiMessageManager;
|
||||||
|
_usageStatisticsManager = usageStatisticsManager;
|
||||||
|
_premiumPackageManager = premiumPackageManager;
|
||||||
|
_aiGateWayManager = aiGateWayManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task AgentCompleteChatStreamAsync(HttpContext httpContext,
|
public async Task AgentCompleteChatStreamAsync(HttpContext httpContext,
|
||||||
Guid sessionId,
|
Guid sessionId,
|
||||||
string content,
|
string content,
|
||||||
|
string token,
|
||||||
Guid tokenId,
|
Guid tokenId,
|
||||||
string modelId,
|
string modelId,
|
||||||
Guid userId,
|
Guid userId,
|
||||||
List<string> tools
|
List<string> tools
|
||||||
, CancellationToken cancellationToken)
|
, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
|
||||||
// HttpClient.DefaultProxy = new WebProxy("127.0.0.1:8888");
|
// HttpClient.DefaultProxy = new WebProxy("127.0.0.1:8888");
|
||||||
var response = httpContext.Response;
|
var response = httpContext.Response;
|
||||||
// 设置响应头,声明是 SSE 流
|
// 设置响应头,声明是 SSE 流
|
||||||
@@ -63,16 +64,37 @@ public class ChatManager : DomainService
|
|||||||
response.Headers.TryAdd("Cache-Control", "no-cache");
|
response.Headers.TryAdd("Cache-Control", "no-cache");
|
||||||
response.Headers.TryAdd("Connection", "keep-alive");
|
response.Headers.TryAdd("Connection", "keep-alive");
|
||||||
|
|
||||||
|
var modelDescribe=await _aiGateWayManager.GetModelAsync(ModelApiTypeEnum.OpenAi,modelId);
|
||||||
|
|
||||||
//token状态检查,在应用层统一处理
|
//token状态检查,在应用层统一处理
|
||||||
var token = await _tokenRepository.GetFirstAsync(x => x.Id == tokenId);
|
var client = new OpenAIClient(new ApiKeyCredential(token),
|
||||||
var client = new OpenAIClient(new ApiKeyCredential(token.Token),
|
|
||||||
new OpenAIClientOptions
|
new OpenAIClientOptions
|
||||||
{
|
{
|
||||||
Endpoint = new Uri("https://yxai.chat/v1"),
|
Endpoint = new Uri("https://yxai.chat/v1"),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#pragma warning disable OPENAI001
|
||||||
var agent = client.GetChatClient(modelId)
|
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数据库中获取
|
//线程根据sessionId数据库中获取
|
||||||
var agentStore =
|
var agentStore =
|
||||||
@@ -103,7 +125,8 @@ public class ChatManager : DomainService
|
|||||||
ToolMode = ChatToolMode.Auto
|
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)
|
foreach (var updateContent in update.Contents)
|
||||||
@@ -120,7 +143,7 @@ public class ChatManager : DomainService
|
|||||||
},
|
},
|
||||||
isDone: false, cancellationToken);
|
isDone: false, cancellationToken);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
//工具调用完成
|
//工具调用完成
|
||||||
case FunctionResultContent functionResult:
|
case FunctionResultContent functionResult:
|
||||||
await SendHttpStreamMessageAsync(httpContext,
|
await SendHttpStreamMessageAsync(httpContext,
|
||||||
@@ -131,7 +154,7 @@ public class ChatManager : DomainService
|
|||||||
},
|
},
|
||||||
isDone: false, cancellationToken);
|
isDone: false, cancellationToken);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
//内容输出
|
//内容输出
|
||||||
case TextContent textContent:
|
case TextContent textContent:
|
||||||
//发送消息给前端
|
//发送消息给前端
|
||||||
@@ -143,24 +166,50 @@ public class ChatManager : DomainService
|
|||||||
},
|
},
|
||||||
isDone: false, cancellationToken);
|
isDone: false, cancellationToken);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
//用量统计
|
//用量统计
|
||||||
case UsageContent usageContent:
|
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,
|
await SendHttpStreamMessageAsync(httpContext,
|
||||||
new AgentResultOutput
|
new AgentResultOutput
|
||||||
{
|
{
|
||||||
TypeEnum = AgentResultTypeEnum.Usage,
|
TypeEnum = update.RawRepresentation is ChatResponseUpdate raw
|
||||||
Content = new ThorUsageResponse
|
? raw.FinishReason?.Value == "tool_calls"
|
||||||
{
|
? AgentResultTypeEnum.ToolCallUsage
|
||||||
InputTokens = Convert.ToInt32(usageContent.Details.InputTokenCount ?? 0),
|
: AgentResultTypeEnum.Usage
|
||||||
OutputTokens = Convert.ToInt32(usageContent.Details.OutputTokenCount ?? 0),
|
: AgentResultTypeEnum.Usage,
|
||||||
TotalTokens = usageContent.Details.TotalTokenCount ?? 0,
|
Content = usage!
|
||||||
}
|
|
||||||
},
|
},
|
||||||
isDone: false, cancellationToken);
|
isDone: false, cancellationToken);
|
||||||
Console.WriteLine();
|
|
||||||
Console.WriteLine($"✅ 用量统计: {usageContent.Details.TotalTokenCount}");
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -220,7 +269,7 @@ public class ChatManager : DomainService
|
|||||||
}
|
}
|
||||||
else
|
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);
|
await response.WriteAsync($"data: {output}\n\n", Encoding.UTF8, cancellationToken).ConfigureAwait(false);
|
||||||
|
|||||||
Reference in New Issue
Block a user