refactor for better support of streaming and etc ...
This commit is contained in:
@@ -1,7 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using xAiApi.Interfaces;
|
using xAiApi.Interfaces;
|
||||||
@@ -12,7 +14,7 @@ using xCommons.Providers;
|
|||||||
namespace xAiApi.Controllers
|
namespace xAiApi.Controllers
|
||||||
{
|
{
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[Route ("[controller]")]
|
[Route("[controller]")]
|
||||||
public abstract class XAiServiceControllerBase<TAiService> : XBaseController, IXAiServiceControllerBase
|
public abstract class XAiServiceControllerBase<TAiService> : XBaseController, IXAiServiceControllerBase
|
||||||
where TAiService : IXAiServiceBase
|
where TAiService : IXAiServiceBase
|
||||||
{
|
{
|
||||||
@@ -39,8 +41,8 @@ namespace xAiApi.Controllers
|
|||||||
/// <param name="cancellationToken"></param>
|
/// <param name="cancellationToken"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[HttpGet("Ask")]
|
[HttpGet("AskText")]
|
||||||
public virtual async Task<ActionResult<string>> Ask(
|
public virtual async Task<ActionResult<string>> AskText(
|
||||||
[FromQuery] string question,
|
[FromQuery] string question,
|
||||||
CancellationToken cancellationToken = default
|
CancellationToken cancellationToken = default
|
||||||
)
|
)
|
||||||
@@ -53,7 +55,7 @@ namespace xAiApi.Controllers
|
|||||||
ValidationProvider.NotEmpty(question);
|
ValidationProvider.NotEmpty(question);
|
||||||
|
|
||||||
//
|
//
|
||||||
var result = await aiService.AskAsync(
|
var result = await aiService.AskTextAsync(
|
||||||
prompt: question,
|
prompt: question,
|
||||||
cancellationToken: cancellationToken
|
cancellationToken: cancellationToken
|
||||||
);
|
);
|
||||||
@@ -68,5 +70,85 @@ namespace xAiApi.Controllers
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ask Question and Get Response Stream ...
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="question"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[AllowAnonymous]
|
||||||
|
[HttpGet("AskTextStream")]
|
||||||
|
public virtual async Task AskTextStream(
|
||||||
|
[FromQuery] string question,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//
|
||||||
|
// Validate ...
|
||||||
|
ValidationProvider.NotEmpty(question);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Configure SSE Response Headers ...
|
||||||
|
Response.Headers["X-Accel-Buffering"] = "no";
|
||||||
|
Response.Headers["Connection"] = "keep-alive";
|
||||||
|
Response.Headers["Content-Type"] = "text/event-stream";
|
||||||
|
Response.Headers["Cache-Control"] = "no-cache, no-store";
|
||||||
|
|
||||||
|
//
|
||||||
|
// Disable response buffering ...
|
||||||
|
var responseBodyFeature = HttpContext.Features
|
||||||
|
.Get<Microsoft.AspNetCore.Http.Features.IHttpResponseBodyFeature>();
|
||||||
|
responseBodyFeature?.DisableBuffering();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Stream Response ...
|
||||||
|
var jsonOptions = new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
var enumerable = aiService.AskTextAsEnumerable(
|
||||||
|
prompt: question,
|
||||||
|
cancellationToken: cancellationToken
|
||||||
|
);
|
||||||
|
|
||||||
|
//
|
||||||
|
await foreach (var chunk in enumerable)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
// Skip empty chunks ...
|
||||||
|
if (string.IsNullOrEmpty(chunk))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Build SSE message ...
|
||||||
|
// Format: data: <json>\n\n
|
||||||
|
// var payload = JsonSerializer.Serialize(
|
||||||
|
// new { text = chunk },
|
||||||
|
// jsonOptions
|
||||||
|
// );
|
||||||
|
var payload = chunk;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Write SSE Event ...
|
||||||
|
await Response.WriteAsync($"{payload}\n\n", cancellationToken);
|
||||||
|
await Response.Body.FlushAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
await Response.Body.FlushAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
await Response.Body.FlushAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -34,11 +34,9 @@ namespace xAiApi.Interfaces
|
|||||||
);
|
);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Access Embedding Client ...
|
/// Embedding Generator ...
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
OllamaApiClient GetEmbeddingClient();
|
|
||||||
|
|
||||||
IEmbeddingGenerator<string, Embedding<float>> GetEmbeddingGenerator();
|
IEmbeddingGenerator<string, Embedding<float>> GetEmbeddingGenerator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,7 @@ namespace xAiApi.Interfaces
|
|||||||
/// <param name="prompt"></param>
|
/// <param name="prompt"></param>
|
||||||
/// <param name="cancellationToken"></param>
|
/// <param name="cancellationToken"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task<string> AskAsync(
|
Task<string> AskTextAsync(
|
||||||
string prompt,
|
string prompt,
|
||||||
CancellationToken cancellationToken = default
|
CancellationToken cancellationToken = default
|
||||||
);
|
);
|
||||||
@@ -29,7 +29,7 @@ namespace xAiApi.Interfaces
|
|||||||
/// <param name="prompt"></param>
|
/// <param name="prompt"></param>
|
||||||
/// <param name="cancellationToken"></param>
|
/// <param name="cancellationToken"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
IAsyncEnumerable<string> AskAsEnumerable(
|
IAsyncEnumerable<string> AskTextAsEnumerable(
|
||||||
string prompt,
|
string prompt,
|
||||||
CancellationToken cancellationToken = default
|
CancellationToken cancellationToken = default
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,8 +12,20 @@ namespace xAiApi.Interfaces
|
|||||||
/// <param name="question"></param>
|
/// <param name="question"></param>
|
||||||
/// <param name="cancellationToken"></param>
|
/// <param name="cancellationToken"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpGet("Ask")]
|
[HttpGet("AskText")]
|
||||||
Task<ActionResult<string>> Ask(
|
Task<ActionResult<string>> AskText(
|
||||||
|
[FromQuery] string question,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ask Question and Get Response Stream ...
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="question"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpGet("AskTextStream")]
|
||||||
|
Task AskTextStream(
|
||||||
[FromQuery] string question,
|
[FromQuery] string question,
|
||||||
CancellationToken cancellationToken = default
|
CancellationToken cancellationToken = default
|
||||||
);
|
);
|
||||||
|
|||||||
+10
-41
@@ -47,7 +47,7 @@ namespace xAiApi.Providers
|
|||||||
/// <param name="prompt"></param>
|
/// <param name="prompt"></param>
|
||||||
/// <param name="cancellationToken"></param>
|
/// <param name="cancellationToken"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public virtual async Task<string> AskAsync(
|
public virtual async Task<string> AskTextAsync(
|
||||||
string prompt,
|
string prompt,
|
||||||
CancellationToken cancellationToken = default
|
CancellationToken cancellationToken = default
|
||||||
)
|
)
|
||||||
@@ -81,10 +81,6 @@ namespace xAiApi.Providers
|
|||||||
// Retrieve Response Text ...
|
// Retrieve Response Text ...
|
||||||
var result = response.Text;
|
var result = response.Text;
|
||||||
|
|
||||||
//
|
|
||||||
// Dispose Client ...
|
|
||||||
client.Dispose();
|
|
||||||
|
|
||||||
//
|
//
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -95,7 +91,7 @@ namespace xAiApi.Providers
|
|||||||
/// <param name="prompt"></param>
|
/// <param name="prompt"></param>
|
||||||
/// <param name="cancellationToken"></param>
|
/// <param name="cancellationToken"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public virtual async IAsyncEnumerable<string> AskAsEnumerable(
|
public virtual async IAsyncEnumerable<string> AskTextAsEnumerable(
|
||||||
string prompt,
|
string prompt,
|
||||||
[EnumeratorCancellation]
|
[EnumeratorCancellation]
|
||||||
CancellationToken cancellationToken = default
|
CancellationToken cancellationToken = default
|
||||||
@@ -117,7 +113,6 @@ namespace xAiApi.Providers
|
|||||||
);
|
);
|
||||||
|
|
||||||
//
|
//
|
||||||
var result = "";
|
|
||||||
await foreach (var res in enumerable)
|
await foreach (var res in enumerable)
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
@@ -128,15 +123,8 @@ namespace xAiApi.Providers
|
|||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
result += res.Text;
|
yield return res.Text;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// Dispose Client ...
|
|
||||||
client.Dispose();
|
|
||||||
|
|
||||||
//
|
|
||||||
yield return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -172,6 +160,7 @@ namespace xAiApi.Providers
|
|||||||
var ollamaClient = new OllamaApiClient(httpClient, model);
|
var ollamaClient = new OllamaApiClient(httpClient, model);
|
||||||
result =
|
result =
|
||||||
new ChatClientBuilder(ollamaClient)
|
new ChatClientBuilder(ollamaClient)
|
||||||
|
.UseFunctionInvocation()
|
||||||
.Build();
|
.Build();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -188,9 +177,12 @@ namespace xAiApi.Providers
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
result =
|
result =
|
||||||
|
new ChatClientBuilder(
|
||||||
openAiClient
|
openAiClient
|
||||||
.GetChatClient(model)
|
.GetChatClient(model)
|
||||||
.AsIChatClient();
|
.AsIChatClient())
|
||||||
|
.UseFunctionInvocation()
|
||||||
|
.Build();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -206,29 +198,6 @@ namespace xAiApi.Providers
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// //
|
|
||||||
// if (isOllamaBased)
|
|
||||||
// {
|
|
||||||
// }
|
|
||||||
// else if (isDeepSeekBased)
|
|
||||||
// {
|
|
||||||
// //
|
|
||||||
// if (apiKey is null ||
|
|
||||||
// string.IsNullOrWhiteSpace(apiKey))
|
|
||||||
// {
|
|
||||||
// throw new Exception("Inavlid Args ...");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// //
|
|
||||||
// var deepseekClient = new DeepSeekChatClient(apiKey: apiKey);
|
|
||||||
// result = deepseekClient;
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// //
|
|
||||||
// // TODO: Implement this ...
|
|
||||||
// }
|
|
||||||
|
|
||||||
//
|
//
|
||||||
if (result.IsNullOrDefault())
|
if (result.IsNullOrDefault())
|
||||||
{
|
{
|
||||||
@@ -330,7 +299,7 @@ namespace xAiApi.Providers
|
|||||||
// Preparing Introduction Prompt ...
|
// Preparing Introduction Prompt ...
|
||||||
var introductionPrompt = new ChatMessage(
|
var introductionPrompt = new ChatMessage(
|
||||||
ChatRole.System,
|
ChatRole.System,
|
||||||
configuration.GetPrompt()
|
configuration.GetPrompt(XAiApiConstants.XAiApiIntroductionPromptName)
|
||||||
);
|
);
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -339,7 +308,7 @@ namespace xAiApi.Providers
|
|||||||
introductionPrompt,
|
introductionPrompt,
|
||||||
];
|
];
|
||||||
if (forceMessage &&
|
if (forceMessage &&
|
||||||
message is not null)
|
!message.IsNullOrDefault())
|
||||||
{
|
{
|
||||||
result.Add(message);
|
result.Add(message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,11 +37,7 @@ namespace xAiApi.Providers
|
|||||||
XAiApiConfiguration configuration,
|
XAiApiConfiguration configuration,
|
||||||
ILogger<XAiEmbeddingServiceBase> logger,
|
ILogger<XAiEmbeddingServiceBase> logger,
|
||||||
string embeddingModelName = XAiApiConstants.XAiEmbeddingModelName
|
string embeddingModelName = XAiApiConstants.XAiEmbeddingModelName
|
||||||
) : base(
|
) : base(dataProvider, logger, configuration)
|
||||||
dataProvider,
|
|
||||||
logger,
|
|
||||||
configuration
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
// Prepare Embedding Model Descriptor ...
|
// Prepare Embedding Model Descriptor ...
|
||||||
@@ -75,15 +71,13 @@ namespace xAiApi.Providers
|
|||||||
|
|
||||||
//
|
//
|
||||||
using var generator = GetEmbeddingGenerator();
|
using var generator = GetEmbeddingGenerator();
|
||||||
|
|
||||||
|
//
|
||||||
GeneratedEmbeddings<Embedding<float>> response = null;
|
GeneratedEmbeddings<Embedding<float>> response = null;
|
||||||
try
|
response = await generator.GenerateAsync(
|
||||||
{
|
values: [content],
|
||||||
response = await generator.GenerateAsync([content]);
|
cancellationToken: cancellationToken
|
||||||
}
|
);
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
logger.LogError(ex.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
if (response.IsNullOrDefault())
|
if (response.IsNullOrDefault())
|
||||||
@@ -103,40 +97,6 @@ namespace xAiApi.Providers
|
|||||||
XException.ActionFailed.Throw();
|
XException.ActionFailed.Throw();
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// Retrieve Ollama Client ...
|
|
||||||
// using var client = GetEmbeddingClient();
|
|
||||||
|
|
||||||
// //
|
|
||||||
// // Create Embedding Request ....
|
|
||||||
// var request = new EmbedRequest
|
|
||||||
// {
|
|
||||||
// Input = [content],
|
|
||||||
// Model = descriptor.LLM,
|
|
||||||
// };
|
|
||||||
|
|
||||||
// //
|
|
||||||
// // Make Request and Recieve Response ...
|
|
||||||
// var response = await client.EmbedAsync(
|
|
||||||
// request: request,
|
|
||||||
// cancellationToken: cancellationToken
|
|
||||||
// );
|
|
||||||
// if (response.IsNullOrDefault())
|
|
||||||
// {
|
|
||||||
// XException.ActionFailed.Throw();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// //
|
|
||||||
// // Generae Result ...
|
|
||||||
// var result = response?
|
|
||||||
// .Embeddings?
|
|
||||||
// .FirstOrDefault()
|
|
||||||
// ?? [];
|
|
||||||
// if (result.Length == 0)
|
|
||||||
// {
|
|
||||||
// XException.ActionFailed.Throw();
|
|
||||||
// }
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Normalize ...
|
// Normalize ...
|
||||||
VectorHelper.Normalize(result);
|
VectorHelper.Normalize(result);
|
||||||
@@ -166,41 +126,25 @@ namespace xAiApi.Providers
|
|||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Retrieve Ollama Client ...
|
using var generator = GetEmbeddingGenerator();
|
||||||
using var client = GetEmbeddingClient();
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Create Embedding Request ....
|
GeneratedEmbeddings<Embedding<float>> response = null;
|
||||||
var request = new EmbedRequest
|
response = await generator.GenerateAsync(
|
||||||
{
|
values: [.. batch],
|
||||||
Input = [.. batch],
|
|
||||||
Model = descriptor.LLM,
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Make Request and Recieve Response ...
|
|
||||||
var response = await client.EmbedAsync(
|
|
||||||
request: request,
|
|
||||||
cancellationToken: cancellationToken
|
cancellationToken: cancellationToken
|
||||||
);
|
);
|
||||||
|
|
||||||
|
//
|
||||||
if (response.IsNullOrDefault())
|
if (response.IsNullOrDefault())
|
||||||
{
|
{
|
||||||
XException.ActionFailed.Throw();
|
XException.ActionFailed.Throw();
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Generae Result ...
|
var result = response
|
||||||
var result = response?
|
.Select(e => e.Vector.ToArray())
|
||||||
.Embeddings
|
.ToList();
|
||||||
.ToArray()
|
|
||||||
?? [];
|
|
||||||
|
|
||||||
//
|
|
||||||
// Validate Result ...
|
|
||||||
if (result.Length == 0)
|
|
||||||
{
|
|
||||||
XException.ActionFailed.Throw();
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Normalize ...
|
// Normalize ...
|
||||||
@@ -214,18 +158,9 @@ namespace xAiApi.Providers
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Access Embedding Client ...
|
/// Embedding Generator ...
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public OllamaApiClient GetEmbeddingClient()
|
|
||||||
{
|
|
||||||
//
|
|
||||||
var result = new OllamaApiClient(new Uri(descriptor.Url));
|
|
||||||
|
|
||||||
//
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEmbeddingGenerator<string, Embedding<float>> GetEmbeddingGenerator()
|
public IEmbeddingGenerator<string, Embedding<float>> GetEmbeddingGenerator()
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
|
|||||||
Reference in New Issue
Block a user