feat: 支持非流式传输
This commit is contained in:
@@ -58,5 +58,10 @@ namespace Yi.Framework.SqlSugarCore.Abstractions
|
|||||||
/// 是否启用SaaS多租户
|
/// 是否启用SaaS多租户
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool EnabledSaasMultiTenancy { get; set; } = false;
|
public bool EnabledSaasMultiTenancy { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 并发乐观锁异常,否则不处理
|
||||||
|
/// </summary>
|
||||||
|
public bool EnabledConcurrencyException { get; set; } = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,11 +2,13 @@
|
|||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using Nito.AsyncEx;
|
using Nito.AsyncEx;
|
||||||
using SqlSugar;
|
using SqlSugar;
|
||||||
using Volo.Abp;
|
using Volo.Abp;
|
||||||
using Volo.Abp.Auditing;
|
using Volo.Abp.Auditing;
|
||||||
using Volo.Abp.Data;
|
using Volo.Abp.Data;
|
||||||
|
using Volo.Abp.DependencyInjection;
|
||||||
using Volo.Abp.Domain.Entities;
|
using Volo.Abp.Domain.Entities;
|
||||||
using Volo.Abp.Domain.Repositories;
|
using Volo.Abp.Domain.Repositories;
|
||||||
using Volo.Abp.Linq;
|
using Volo.Abp.Linq;
|
||||||
@@ -24,6 +26,9 @@ namespace Yi.Framework.SqlSugarCore.Repositories
|
|||||||
|
|
||||||
private readonly ISugarDbContextProvider<ISqlSugarDbContext> _dbContextProvider;
|
private readonly ISugarDbContextProvider<ISqlSugarDbContext> _dbContextProvider;
|
||||||
|
|
||||||
|
public IAbpLazyServiceProvider LazyServiceProvider { get; set; }
|
||||||
|
protected DbConnOptions? Options => LazyServiceProvider?.LazyGetService<IOptions<DbConnOptions>>().Value;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 异步查询执行器
|
/// 异步查询执行器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -407,17 +412,26 @@ namespace Yi.Framework.SqlSugarCore.Repositories
|
|||||||
{
|
{
|
||||||
if (typeof(TEntity).IsAssignableTo<IHasConcurrencyStamp>()) //带版本号乐观锁更新
|
if (typeof(TEntity).IsAssignableTo<IHasConcurrencyStamp>()) //带版本号乐观锁更新
|
||||||
{
|
{
|
||||||
try
|
if (Options is not null && Options.EnabledConcurrencyException)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int num = await (await GetDbSimpleClientAsync())
|
||||||
|
.Context.Updateable(updateObj).ExecuteCommandWithOptLockAsync(true);
|
||||||
|
return num > 0;
|
||||||
|
}
|
||||||
|
catch (VersionExceptions ex)
|
||||||
|
{
|
||||||
|
throw new AbpDbConcurrencyException(
|
||||||
|
$"{ex.Message}[更新失败:ConcurrencyStamp不是最新版本],entityInfo:{updateObj}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
int num = await (await GetDbSimpleClientAsync())
|
int num = await (await GetDbSimpleClientAsync())
|
||||||
.Context.Updateable(updateObj).ExecuteCommandWithOptLockAsync(true);
|
.Context.Updateable(updateObj).ExecuteCommandAsync();
|
||||||
return num > 0;
|
return num > 0;
|
||||||
}
|
}
|
||||||
catch (VersionExceptions ex)
|
|
||||||
{
|
|
||||||
throw new AbpDbConcurrencyException(
|
|
||||||
$"{ex.Message}[更新失败:ConcurrencyStamp不是最新版本],entityInfo:{updateObj}", ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return await (await GetDbSimpleClientAsync()).UpdateAsync(updateObj);
|
return await (await GetDbSimpleClientAsync()).UpdateAsync(updateObj);
|
||||||
@@ -441,8 +455,8 @@ namespace Yi.Framework.SqlSugarCore.Repositories
|
|||||||
public class SqlSugarRepository<TEntity, TKey> : SqlSugarRepository<TEntity>, ISqlSugarRepository<TEntity, TKey>,
|
public class SqlSugarRepository<TEntity, TKey> : SqlSugarRepository<TEntity>, ISqlSugarRepository<TEntity, TKey>,
|
||||||
IRepository<TEntity, TKey> where TEntity : class, IEntity<TKey>, new()
|
IRepository<TEntity, TKey> where TEntity : class, IEntity<TKey>, new()
|
||||||
{
|
{
|
||||||
public SqlSugarRepository(ISugarDbContextProvider<ISqlSugarDbContext> sugarDbContextProvider) : base(
|
public SqlSugarRepository(ISugarDbContextProvider<ISqlSugarDbContext> dbContextProvider) : base(
|
||||||
sugarDbContextProvider)
|
dbContextProvider)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.OpenAi;
|
||||||
|
|
||||||
|
public class ChatCompletionsOutput
|
||||||
|
{
|
||||||
|
[JsonProperty("id")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
[JsonProperty("object")]
|
||||||
|
public string Object { get; set; }
|
||||||
|
[JsonProperty("created_at")]
|
||||||
|
public long CreatedAt { get; set; }
|
||||||
|
[JsonProperty("status")]
|
||||||
|
public string Status { get; set; }
|
||||||
|
[JsonProperty("error")]
|
||||||
|
public object Error { get; set; }
|
||||||
|
[JsonProperty("incomplete_details")]
|
||||||
|
public object IncompleteDetails { get; set; }
|
||||||
|
[JsonProperty("instructions")]
|
||||||
|
public object Instructions { get; set; }
|
||||||
|
[JsonProperty("max_output_tokens")]
|
||||||
|
public object MaxOutputTokens { get; set; }
|
||||||
|
[JsonProperty("model")]
|
||||||
|
public string Model { get; set; }
|
||||||
|
[JsonProperty("output")]
|
||||||
|
public List<Output> Output { get; set; }
|
||||||
|
[JsonProperty("parallel_tool_calls")]
|
||||||
|
public bool ParallelToolCalls { get; set; }
|
||||||
|
[JsonProperty("previous_response_id")]
|
||||||
|
public object PreviousResponseId { get; set; }
|
||||||
|
[JsonProperty("reasoning")]
|
||||||
|
public Reasoning Reasoning { get; set; }
|
||||||
|
[JsonProperty("store")]
|
||||||
|
public bool Store { get; set; }
|
||||||
|
[JsonProperty("temperature")]
|
||||||
|
public double Temperature { get; set; }
|
||||||
|
[JsonProperty("text")]
|
||||||
|
public Text Text { get; set; }
|
||||||
|
[JsonProperty("tool_choice")]
|
||||||
|
public string ToolChoice { get; set; }
|
||||||
|
[JsonProperty("tools")]
|
||||||
|
public List<object> Tools { get; set; }
|
||||||
|
[JsonProperty("top_p")]
|
||||||
|
public double TopP { get; set; }
|
||||||
|
[JsonProperty("truncation")]
|
||||||
|
public string Truncation { get; set; }
|
||||||
|
[JsonProperty("usage")]
|
||||||
|
public Usage Usage { get; set; }
|
||||||
|
[JsonProperty("user")]
|
||||||
|
public object User { get; set; }
|
||||||
|
[JsonProperty("metadata")]
|
||||||
|
public Dictionary<string, object> Metadata { get; set; }
|
||||||
|
}
|
||||||
|
public class Output
|
||||||
|
{
|
||||||
|
[JsonProperty("type")]
|
||||||
|
public string Type { get; set; }
|
||||||
|
[JsonProperty("id")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
[JsonProperty("status")]
|
||||||
|
public string Status { get; set; }
|
||||||
|
[JsonProperty("role")]
|
||||||
|
public string Role { get; set; }
|
||||||
|
[JsonProperty("content")]
|
||||||
|
public List<Content> Content { get; set; }
|
||||||
|
}
|
||||||
|
public class Content
|
||||||
|
{
|
||||||
|
[JsonProperty("type")]
|
||||||
|
public string Type { get; set; }
|
||||||
|
[JsonProperty("text")]
|
||||||
|
public string Text { get; set; }
|
||||||
|
[JsonProperty("annotations")]
|
||||||
|
public List<object> Annotations { get; set; }
|
||||||
|
}
|
||||||
|
public class Reasoning
|
||||||
|
{
|
||||||
|
[JsonProperty("effort")]
|
||||||
|
public object Effort { get; set; }
|
||||||
|
[JsonProperty("summary")]
|
||||||
|
public object Summary { get; set; }
|
||||||
|
}
|
||||||
|
public class Text
|
||||||
|
{
|
||||||
|
[JsonProperty("format")]
|
||||||
|
public Format Format { get; set; }
|
||||||
|
}
|
||||||
|
public class Format
|
||||||
|
{
|
||||||
|
[JsonProperty("type")]
|
||||||
|
public string Type { get; set; }
|
||||||
|
}
|
||||||
|
public class Usage
|
||||||
|
{
|
||||||
|
[JsonProperty("input_tokens")]
|
||||||
|
public int InputTokens { get; set; }
|
||||||
|
[JsonProperty("input_tokens_details")]
|
||||||
|
public InputTokensDetails InputTokensDetails { get; set; }
|
||||||
|
[JsonProperty("output_tokens")]
|
||||||
|
public int OutputTokens { get; set; }
|
||||||
|
[JsonProperty("output_tokens_details")]
|
||||||
|
public OutputTokensDetails OutputTokensDetails { get; set; }
|
||||||
|
[JsonProperty("total_tokens")]
|
||||||
|
public int TotalTokens { get; set; }
|
||||||
|
}
|
||||||
|
public class InputTokensDetails
|
||||||
|
{
|
||||||
|
[JsonProperty("cached_tokens")]
|
||||||
|
public int CachedTokens { get; set; }
|
||||||
|
}
|
||||||
|
public class OutputTokensDetails
|
||||||
|
{
|
||||||
|
[JsonProperty("reasoning_tokens")]
|
||||||
|
public int ReasoningTokens { get; set; }
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace Yi.Framework.AiHub.Application.Contracts.Dtos;
|
namespace Yi.Framework.AiHub.Application.Contracts.Dtos;
|
||||||
|
|
||||||
public class SendMessageOutputDto
|
public class SendMessageStreamOutputDto
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 唯一标识符
|
/// 唯一标识符
|
||||||
@@ -55,6 +55,7 @@ public class OpenApiService : ApplicationService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//是否使用流式传输
|
||||||
if (input.Stream)
|
if (input.Stream)
|
||||||
{
|
{
|
||||||
//ai网关代理httpcontext
|
//ai网关代理httpcontext
|
||||||
@@ -64,8 +65,9 @@ public class OpenApiService : ApplicationService
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await _aiGateWayManager.CompleteChatForStatisticsAsync(_httpContextAccessor.HttpContext,input.Model, history, userId, null,
|
await _aiGateWayManager.CompleteChatForStatisticsAsync(_httpContextAccessor.HttpContext, input.Model,
|
||||||
cancellationToken);
|
history, userId, null,
|
||||||
|
cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Net.Http.Json;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
@@ -62,7 +63,8 @@ public class AzureRestChatService : IChatService
|
|||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
throw new UserFriendlyException($"当前模型不可用:{aiModelDescribe.ModelId},状态码:{response.StatusCode},原因:{response.ReasonPhrase}");
|
throw new UserFriendlyException(
|
||||||
|
$"当前模型不可用:{aiModelDescribe.ModelId},状态码:{response.StatusCode},原因:{response.ReasonPhrase}");
|
||||||
}
|
}
|
||||||
// 确认响应成功
|
// 确认响应成功
|
||||||
// response.EnsureSuccessStatusCode();
|
// response.EnsureSuccessStatusCode();
|
||||||
@@ -76,11 +78,11 @@ public class AzureRestChatService : IChatService
|
|||||||
var result = new CompleteChatResponse();
|
var result = new CompleteChatResponse();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var jsonObj = MapToJObject(line);
|
var jsonObj = MapToStreamJObject(line);
|
||||||
if (jsonObj is not null)
|
if (jsonObj is not null)
|
||||||
{
|
{
|
||||||
var content = GetContent(jsonObj);
|
var content = GetStreamContent(jsonObj);
|
||||||
var tokenUsage = GetTokenUsage(jsonObj);
|
var tokenUsage = GetStreamTokenUsage(jsonObj);
|
||||||
result = new CompleteChatResponse
|
result = new CompleteChatResponse
|
||||||
{
|
{
|
||||||
TokenUsage = tokenUsage,
|
TokenUsage = tokenUsage,
|
||||||
@@ -98,28 +100,85 @@ public class AzureRestChatService : IChatService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<CompleteChatResponse> CompleteChatAsync(AiModelDescribe aiModelDescribe, List<ChatMessage> messages, CancellationToken cancellationToken)
|
public async Task<CompleteChatResponse> CompleteChatAsync(AiModelDescribe aiModelDescribe,
|
||||||
|
List<ChatMessage> messages, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException("暂未实现");
|
// 设置API URL
|
||||||
|
var apiUrl = $"{aiModelDescribe.Endpoint}";
|
||||||
|
|
||||||
|
// 准备请求内容
|
||||||
|
var requestBody = new
|
||||||
|
{
|
||||||
|
messages = messages.Select(x => new
|
||||||
|
{
|
||||||
|
role = x.GetRoleAsString(),
|
||||||
|
content = x.Content.FirstOrDefault()?.Text
|
||||||
|
}).ToList(),
|
||||||
|
stream = false,
|
||||||
|
// max_tokens = 2048,
|
||||||
|
// temperature = 0.8,
|
||||||
|
// top_p = 0.1,
|
||||||
|
// presence_penalty = 0,
|
||||||
|
// frequency_penalty = 0,
|
||||||
|
model = aiModelDescribe.ModelId
|
||||||
|
};
|
||||||
|
|
||||||
|
// 序列化请求内容为JSON
|
||||||
|
string jsonBody = JsonConvert.SerializeObject(requestBody);
|
||||||
|
|
||||||
|
using var httpClient = new HttpClient()
|
||||||
|
{
|
||||||
|
//10分钟超时
|
||||||
|
Timeout = TimeSpan.FromSeconds(600)
|
||||||
|
};
|
||||||
|
// 设置请求头
|
||||||
|
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {aiModelDescribe.ApiKey}");
|
||||||
|
// 其他头信息如Content-Type在StringContent中设置
|
||||||
|
|
||||||
|
// 构造 POST 请求
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Post, apiUrl);
|
||||||
|
|
||||||
|
// 设置请求内容(示例)
|
||||||
|
request.Content = new StringContent(jsonBody, Encoding.UTF8, "application/json");
|
||||||
|
|
||||||
|
// 发送POST请求
|
||||||
|
HttpResponseMessage response =
|
||||||
|
await httpClient.SendAsync(request, cancellationToken);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(
|
||||||
|
$"当前模型不可用:{aiModelDescribe.ModelId},状态码:{response.StatusCode},原因:{response.ReasonPhrase}");
|
||||||
|
}
|
||||||
|
// 确认响应成功
|
||||||
|
// response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
// 读取响应内容
|
||||||
|
var responseStr = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||||
|
var jObject = MapToJObject(responseStr);
|
||||||
|
|
||||||
|
var content = GetContent(jObject);
|
||||||
|
var usage = GetTokenUsage(jObject);
|
||||||
|
var result = new CompleteChatResponse
|
||||||
|
{
|
||||||
|
TokenUsage = usage,
|
||||||
|
IsFinish = true,
|
||||||
|
Content = content
|
||||||
|
};
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private JObject? MapToJObject(string line)
|
private JObject? MapToJObject(string body)
|
||||||
{
|
{
|
||||||
if (line == "data: [DONE]"||string.IsNullOrWhiteSpace(line) )
|
if (string.IsNullOrWhiteSpace(body))
|
||||||
{
|
|
||||||
return null;
|
return null;
|
||||||
}
|
return JObject.Parse(body);
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(line))
|
|
||||||
return null;
|
|
||||||
string prefix = "data: ";
|
|
||||||
line = line.Substring(prefix.Length);
|
|
||||||
return JObject.Parse(line);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string? GetContent(JObject? jsonObj)
|
private string? GetContent(JObject? jsonObj)
|
||||||
{
|
{
|
||||||
var contentToken = jsonObj.SelectToken("choices[0].delta.content");
|
var contentToken = jsonObj.SelectToken("choices[0].message.content");
|
||||||
if (contentToken != null && contentToken.Type != JTokenType.Null)
|
if (contentToken != null && contentToken.Type != JTokenType.Null)
|
||||||
{
|
{
|
||||||
return contentToken.ToString();
|
return contentToken.ToString();
|
||||||
@@ -157,4 +216,60 @@ public class AzureRestChatService : IChatService
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private JObject? MapToStreamJObject(string line)
|
||||||
|
{
|
||||||
|
if (line == "data: [DONE]" || string.IsNullOrWhiteSpace(line))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(line))
|
||||||
|
return null;
|
||||||
|
string prefix = "data: ";
|
||||||
|
line = line.Substring(prefix.Length);
|
||||||
|
return JObject.Parse(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? GetStreamContent(JObject? jsonObj)
|
||||||
|
{
|
||||||
|
var contentToken = jsonObj.SelectToken("choices[0].delta.content");
|
||||||
|
if (contentToken != null && contentToken.Type != JTokenType.Null)
|
||||||
|
{
|
||||||
|
return contentToken.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TokenUsage? GetStreamTokenUsage(JObject? jsonObj)
|
||||||
|
{
|
||||||
|
var usage = jsonObj.SelectToken("usage");
|
||||||
|
if (usage is not null && usage.Type != JTokenType.Null)
|
||||||
|
{
|
||||||
|
var result = new TokenUsage();
|
||||||
|
var completionTokens = usage["completion_tokens"];
|
||||||
|
if (completionTokens is not null && completionTokens.Type != JTokenType.Null)
|
||||||
|
{
|
||||||
|
result.OutputTokenCount = completionTokens.ToObject<int>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var promptTokens = usage["prompt_tokens"];
|
||||||
|
if (promptTokens is not null && promptTokens.Type != JTokenType.Null)
|
||||||
|
{
|
||||||
|
result.InputTokenCount = promptTokens.ToObject<int>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalTokens = usage["total_tokens"];
|
||||||
|
if (totalTokens is not null && totalTokens.Type != JTokenType.Null)
|
||||||
|
{
|
||||||
|
result.TotalTokenCount = totalTokens.ToObject<int>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -9,11 +9,13 @@ using Newtonsoft.Json.Serialization;
|
|||||||
using OpenAI.Chat;
|
using OpenAI.Chat;
|
||||||
using Volo.Abp.Domain.Services;
|
using Volo.Abp.Domain.Services;
|
||||||
using Yi.Framework.AiHub.Application.Contracts.Dtos;
|
using Yi.Framework.AiHub.Application.Contracts.Dtos;
|
||||||
|
using Yi.Framework.AiHub.Application.Contracts.Dtos.OpenAi;
|
||||||
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.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;
|
||||||
|
using Usage = Yi.Framework.AiHub.Application.Contracts.Dtos.Usage;
|
||||||
|
|
||||||
namespace Yi.Framework.AiHub.Domain.Managers;
|
namespace Yi.Framework.AiHub.Domain.Managers;
|
||||||
|
|
||||||
@@ -107,7 +109,7 @@ public class AiGateWayManager : DomainService
|
|||||||
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 modelDescribe = await GetModelAsync(modelId);
|
var modelDescribe = await GetModelAsync(modelId);
|
||||||
var chatService = LazyServiceProvider.GetRequiredKeyedService<IChatService>(modelDescribe.HandlerName);
|
var chatService = LazyServiceProvider.GetRequiredKeyedService<IChatService>(modelDescribe.HandlerName);
|
||||||
var output = await chatService.CompleteChatAsync(modelDescribe, messages, cancellationToken);
|
var data = await chatService.CompleteChatAsync(modelDescribe, messages, cancellationToken);
|
||||||
if (userId is not null)
|
if (userId is not null)
|
||||||
{
|
{
|
||||||
await _aiMessageManager.CreateUserMessageAsync(userId.Value, sessionId,
|
await _aiMessageManager.CreateUserMessageAsync(userId.Value, sessionId,
|
||||||
@@ -115,22 +117,23 @@ public class AiGateWayManager : DomainService
|
|||||||
{
|
{
|
||||||
Content = messages.LastOrDefault().Content.FirstOrDefault()?.Text ?? string.Empty,
|
Content = messages.LastOrDefault().Content.FirstOrDefault()?.Text ?? string.Empty,
|
||||||
ModelId = modelId,
|
ModelId = modelId,
|
||||||
TokenUsage = output.TokenUsage,
|
TokenUsage = data.TokenUsage,
|
||||||
});
|
});
|
||||||
|
|
||||||
await _aiMessageManager.CreateSystemMessageAsync(userId.Value, sessionId,
|
await _aiMessageManager.CreateSystemMessageAsync(userId.Value, sessionId,
|
||||||
new MessageInputDto
|
new MessageInputDto
|
||||||
{
|
{
|
||||||
Content = output.Content,
|
Content = data.Content,
|
||||||
ModelId = modelId,
|
ModelId = modelId,
|
||||||
TokenUsage = output.TokenUsage
|
TokenUsage = data.TokenUsage
|
||||||
});
|
});
|
||||||
|
|
||||||
await _usageStatisticsManager.SetUsageAsync(userId.Value, modelId, output.TokenUsage.InputTokenCount,
|
await _usageStatisticsManager.SetUsageAsync(userId.Value, modelId, data.TokenUsage.InputTokenCount,
|
||||||
output.TokenUsage.OutputTokenCount);
|
data.TokenUsage.OutputTokenCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
var body = JsonConvert.SerializeObject(output, new JsonSerializerSettings
|
var result = MapToChatCompletions(modelId, data.Content);
|
||||||
|
var body = JsonConvert.SerializeObject(result, new JsonSerializerSettings
|
||||||
{
|
{
|
||||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||||
});
|
});
|
||||||
@@ -261,9 +264,9 @@ public class AiGateWayManager : DomainService
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private SendMessageOutputDto MapToMessage(string modelId, string content)
|
private SendMessageStreamOutputDto MapToMessage(string modelId, string content)
|
||||||
{
|
{
|
||||||
var output = new SendMessageOutputDto
|
var output = new SendMessageStreamOutputDto
|
||||||
{
|
{
|
||||||
Id = "chatcmpl-BotYP3BlN5T4g9YPnW0fBSBvKzXdd",
|
Id = "chatcmpl-BotYP3BlN5T4g9YPnW0fBSBvKzXdd",
|
||||||
Object = "chat.completion.chunk",
|
Object = "chat.completion.chunk",
|
||||||
@@ -338,4 +341,75 @@ public class AiGateWayManager : DomainService
|
|||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ChatCompletionsOutput MapToChatCompletions(string modelId, string content)
|
||||||
|
{
|
||||||
|
return new ChatCompletionsOutput
|
||||||
|
{
|
||||||
|
Id = "resp_67ccd2bed1ec8190b14f964abc0542670bb6a6b452d3795b",
|
||||||
|
Object = "response",
|
||||||
|
CreatedAt = 1741476542,
|
||||||
|
Status = "completed",
|
||||||
|
Error = null,
|
||||||
|
IncompleteDetails = null,
|
||||||
|
Instructions = null,
|
||||||
|
MaxOutputTokens = null,
|
||||||
|
Model = modelId,
|
||||||
|
Output = new List<Output>()
|
||||||
|
{
|
||||||
|
new Output
|
||||||
|
{
|
||||||
|
Type = "message",
|
||||||
|
Id = "msg_67ccd2bf17f0819081ff3bb2cf6508e60bb6a6b452d3795b",
|
||||||
|
Status = "completed",
|
||||||
|
Role = "assistant",
|
||||||
|
Content = new List<Content>
|
||||||
|
{
|
||||||
|
new Content
|
||||||
|
{
|
||||||
|
Type = "output_text",
|
||||||
|
Text = content,
|
||||||
|
Annotations = new List<object>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ParallelToolCalls = true,
|
||||||
|
PreviousResponseId = null,
|
||||||
|
Reasoning = new Reasoning
|
||||||
|
{
|
||||||
|
Effort = null,
|
||||||
|
Summary = null
|
||||||
|
},
|
||||||
|
Store = true,
|
||||||
|
Temperature = 0,
|
||||||
|
Text = new Text
|
||||||
|
{
|
||||||
|
Format = new Format
|
||||||
|
{
|
||||||
|
Type = "text"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ToolChoice = "auto",
|
||||||
|
Tools = new List<object>(),
|
||||||
|
TopP = 1.0,
|
||||||
|
Truncation = "disabled",
|
||||||
|
Usage = new Application.Contracts.Dtos.OpenAi.Usage
|
||||||
|
{
|
||||||
|
InputTokens = 0,
|
||||||
|
InputTokensDetails = new InputTokensDetails
|
||||||
|
{
|
||||||
|
CachedTokens = 0
|
||||||
|
},
|
||||||
|
OutputTokens = 0,
|
||||||
|
OutputTokensDetails = new OutputTokensDetails
|
||||||
|
{
|
||||||
|
ReasoningTokens = 0
|
||||||
|
},
|
||||||
|
TotalTokens = 0
|
||||||
|
},
|
||||||
|
User = null,
|
||||||
|
Metadata = null
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -35,7 +35,7 @@ namespace Yi.Abp.SqlsugarCore
|
|||||||
{
|
{
|
||||||
public override void ConfigureServices(ServiceConfigurationContext context)
|
public override void ConfigureServices(ServiceConfigurationContext context)
|
||||||
{
|
{
|
||||||
context.Services.AddYiDbContext<YiDbContext>();
|
context.Services.AddYiDbContext<YiDbContext>(options => { options.EnabledConcurrencyException = false; });
|
||||||
//默认不开放,可根据项目需要是否Db直接对外开放
|
//默认不开放,可根据项目需要是否Db直接对外开放
|
||||||
//context.Services.AddTransient(x => x.GetRequiredService<ISqlSugarDbContext>().SqlSugarClient);
|
//context.Services.AddTransient(x => x.GetRequiredService<ISqlSugarDbContext>().SqlSugarClient);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,7 @@ using Hangfire.Redis.StackExchange;
|
|||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
using Microsoft.AspNetCore.Cors;
|
using Microsoft.AspNetCore.Cors;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.Filters;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
|
||||||
using Microsoft.AspNetCore.StaticFiles;
|
using Microsoft.AspNetCore.StaticFiles;
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi.Models;
|
||||||
using StackExchange.Redis;
|
using StackExchange.Redis;
|
||||||
@@ -23,13 +20,10 @@ using Volo.Abp.AspNetCore.MultiTenancy;
|
|||||||
using Volo.Abp.AspNetCore.Mvc;
|
using Volo.Abp.AspNetCore.Mvc;
|
||||||
using Volo.Abp.AspNetCore.Mvc.AntiForgery;
|
using Volo.Abp.AspNetCore.Mvc.AntiForgery;
|
||||||
using Volo.Abp.AspNetCore.Serilog;
|
using Volo.Abp.AspNetCore.Serilog;
|
||||||
using Volo.Abp.AspNetCore.VirtualFileSystem;
|
|
||||||
using Volo.Abp.Auditing;
|
using Volo.Abp.Auditing;
|
||||||
using Volo.Abp.Autofac;
|
using Volo.Abp.Autofac;
|
||||||
using Volo.Abp.BackgroundJobs.Hangfire;
|
|
||||||
using Volo.Abp.BackgroundWorkers;
|
using Volo.Abp.BackgroundWorkers;
|
||||||
using Volo.Abp.Caching;
|
using Volo.Abp.Caching;
|
||||||
using Volo.Abp.Domain.Repositories;
|
|
||||||
using Volo.Abp.MultiTenancy;
|
using Volo.Abp.MultiTenancy;
|
||||||
using Volo.Abp.Swashbuckle;
|
using Volo.Abp.Swashbuckle;
|
||||||
using Yi.Abp.Application;
|
using Yi.Abp.Application;
|
||||||
@@ -44,7 +38,6 @@ using Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection;
|
|||||||
using Yi.Framework.BackgroundWorkers.Hangfire;
|
using Yi.Framework.BackgroundWorkers.Hangfire;
|
||||||
using Yi.Framework.Bbs.Application;
|
using Yi.Framework.Bbs.Application;
|
||||||
using Yi.Framework.Bbs.Application.Extensions;
|
using Yi.Framework.Bbs.Application.Extensions;
|
||||||
using Yi.Framework.Bbs.Domain.Entities.Forum;
|
|
||||||
using Yi.Framework.ChatHub.Application;
|
using Yi.Framework.ChatHub.Application;
|
||||||
using Yi.Framework.CodeGen.Application;
|
using Yi.Framework.CodeGen.Application;
|
||||||
using Yi.Framework.Core.Json;
|
using Yi.Framework.Core.Json;
|
||||||
@@ -53,7 +46,6 @@ using Yi.Framework.Rbac.Application;
|
|||||||
using Yi.Framework.Rbac.Domain.Authorization;
|
using Yi.Framework.Rbac.Domain.Authorization;
|
||||||
using Yi.Framework.Rbac.Domain.Shared.Consts;
|
using Yi.Framework.Rbac.Domain.Shared.Consts;
|
||||||
using Yi.Framework.Rbac.Domain.Shared.Options;
|
using Yi.Framework.Rbac.Domain.Shared.Options;
|
||||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
|
||||||
using Yi.Framework.Stock.Application;
|
using Yi.Framework.Stock.Application;
|
||||||
using Yi.Framework.TenantManagement.Application;
|
using Yi.Framework.TenantManagement.Application;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user