mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-06 23:49:40 +00:00
Added support for Perplexity (#545)
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage deb updater) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Read metadata (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage deb updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage deb updater) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Read metadata (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage deb updater) (push) Blocked by required conditions
Co-authored-by: Thorsten Sommer <mail@tsommer.org>
This commit is contained in:
1 parent
adf61a1384
commit
2a01d3bb6e
39 files changed
+398
-39
No files matched your search
@@ -21,7 +21,7 @@ public sealed class ProviderAlibabaCloud(ILogger logger) : BaseProvider("https:/
|
||||
public override string InstanceName { get; set; } = "AlibabaCloud";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
|
||||
public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
|
||||
@@ -18,7 +18,7 @@ public sealed class ProviderAnthropic(ILogger logger) : BaseProvider("https://ap
|
||||
public override string InstanceName { get; set; } = "Anthropic";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
|
||||
public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
|
||||
@@ -13,7 +13,7 @@ public readonly record struct ResponseStreamLine(string Type, int Index, Delta D
|
||||
public bool ContainsContent() => this != default && !string.IsNullOrWhiteSpace(this.Delta.Text);
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetContent() => this.Delta.Text;
|
||||
public ContentStreamChunk GetContent() => new(this.Delta.Text, []);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -63,7 +63,7 @@ public abstract class BaseProvider : IProvider, ISecretId
|
||||
public abstract string InstanceName { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, CancellationToken token = default);
|
||||
public abstract IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, CancellationToken token = default);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, CancellationToken token = default);
|
||||
@@ -96,7 +96,7 @@ public abstract class BaseProvider : IProvider, ISecretId
|
||||
/// <param name="requestBuilder">A function that builds the request.</param>
|
||||
/// <param name="token">The cancellation token.</param>
|
||||
/// <returns>The status object of the request.</returns>
|
||||
protected async Task<HttpRateLimitedStreamResult> SendRequest(Func<Task<HttpRequestMessage>> requestBuilder, CancellationToken token = default)
|
||||
private async Task<HttpRateLimitedStreamResult> SendRequest(Func<Task<HttpRequestMessage>> requestBuilder, CancellationToken token = default)
|
||||
{
|
||||
const int MAX_RETRIES = 6;
|
||||
const double RETRY_DELAY_SECONDS = 4;
|
||||
@@ -189,7 +189,7 @@ public abstract class BaseProvider : IProvider, ISecretId
|
||||
return new HttpRateLimitedStreamResult(true, false, string.Empty, response);
|
||||
}
|
||||
|
||||
protected async IAsyncEnumerable<string> StreamChatCompletionInternal<T>(string providerName, Func<Task<HttpRequestMessage>> requestBuilder, [EnumeratorCancellation] CancellationToken token = default) where T : struct, IResponseStreamLine
|
||||
protected async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletionInternal<T>(string providerName, Func<Task<HttpRequestMessage>> requestBuilder, [EnumeratorCancellation] CancellationToken token = default) where T : struct, IResponseStreamLine
|
||||
{
|
||||
StreamReader? streamReader = null;
|
||||
try
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace AIStudio.Provider;
|
||||
|
||||
/// <summary>
|
||||
/// A chunk of content from a content stream, along with its associated sources.
|
||||
/// </summary>
|
||||
/// <param name="Content">The text content of the chunk.</param>
|
||||
/// <param name="Sources">The list of sources associated with the chunk.</param>
|
||||
public sealed record ContentStreamChunk(string Content, IList<ISource> Sources)
|
||||
{
|
||||
/// <summary>
|
||||
/// Implicit conversion to string.
|
||||
/// </summary>
|
||||
/// <param name="chunk">The content stream chunk.</param>
|
||||
/// <returns>The text content of the chunk.</returns>
|
||||
public static implicit operator string(ContentStreamChunk chunk) => chunk.Content;
|
||||
}
|
||||
@@ -20,7 +20,7 @@ public sealed class ProviderDeepSeek(ILogger logger) : BaseProvider("https://api
|
||||
public override string InstanceName { get; set; } = "DeepSeek";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
|
||||
public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
|
||||
@@ -19,7 +19,7 @@ public class ProviderFireworks(ILogger logger) : BaseProvider("https://api.firew
|
||||
public override string InstanceName { get; set; } = "Fireworks.ai";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
|
||||
public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
|
||||
@@ -14,7 +14,7 @@ public readonly record struct ResponseStreamLine(string Id, string Object, uint
|
||||
public bool ContainsContent() => this != default && this.Choices.Count > 0;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetContent() => this.Choices[0].Delta.Content;
|
||||
public ContentStreamChunk GetContent() => new(this.Choices[0].Delta.Content, []);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -20,7 +20,7 @@ public sealed class ProviderGWDG(ILogger logger) : BaseProvider("https://chat-ai
|
||||
public override string InstanceName { get; set; } = "GWDG SAIA";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
|
||||
public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
|
||||
@@ -20,7 +20,7 @@ public class ProviderGoogle(ILogger logger) : BaseProvider("https://generativela
|
||||
public override string InstanceName { get; set; } = "Google Gemini";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async IAsyncEnumerable<string> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
|
||||
public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
|
||||
@@ -20,7 +20,7 @@ public class ProviderGroq(ILogger logger) : BaseProvider("https://api.groq.com/o
|
||||
public override string InstanceName { get; set; } = "Groq";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
|
||||
public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
|
||||
@@ -20,7 +20,7 @@ public sealed class ProviderHelmholtz(ILogger logger) : BaseProvider("https://ap
|
||||
public override string InstanceName { get; set; } = "Helmholtz Blablador";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
|
||||
public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
|
||||
@@ -25,7 +25,7 @@ public sealed class ProviderHuggingFace : BaseProvider
|
||||
public override string InstanceName { get; set; } = "HuggingFace";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
|
||||
public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
|
||||
@@ -27,7 +27,7 @@ public interface IProvider
|
||||
/// <param name="settingsManager">The settings manager instance to use.</param>
|
||||
/// <param name="token">The cancellation token.</param>
|
||||
/// <returns>The chat completion stream.</returns>
|
||||
public IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, CancellationToken token = default);
|
||||
public IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// Starts an image completion stream.
|
||||
|
||||
@@ -12,5 +12,17 @@ public interface IResponseStreamLine
|
||||
/// Gets the content of the response line.
|
||||
/// </summary>
|
||||
/// <returns>The content of the response line.</returns>
|
||||
public string GetContent();
|
||||
public ContentStreamChunk GetContent();
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the response line contains any sources.
|
||||
/// </summary>
|
||||
/// <returns>True when the response line contains sources, false otherwise.</returns>
|
||||
public bool ContainsSources() => false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sources of the response line.
|
||||
/// </summary>
|
||||
/// <returns>The sources of the response line.</returns>
|
||||
public IList<ISource> GetSources() => [];
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace AIStudio.Provider;
|
||||
|
||||
/// <summary>
|
||||
/// Data model for a source used in the response.
|
||||
/// </summary>
|
||||
public interface ISource
|
||||
{
|
||||
/// <summary>
|
||||
/// The title of the source.
|
||||
/// </summary>
|
||||
public string Title { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The URL of the source.
|
||||
/// </summary>
|
||||
public string URL { get; }
|
||||
}
|
||||
@@ -14,6 +14,7 @@ public enum LLMProviders
|
||||
X = 8,
|
||||
DEEP_SEEK = 11,
|
||||
ALIBABA_CLOUD = 12,
|
||||
PERPLEXITY = 14,
|
||||
|
||||
FIREWORKS = 5,
|
||||
GROQ = 6,
|
||||
|
||||
@@ -9,6 +9,7 @@ using AIStudio.Provider.Helmholtz;
|
||||
using AIStudio.Provider.HuggingFace;
|
||||
using AIStudio.Provider.Mistral;
|
||||
using AIStudio.Provider.OpenAI;
|
||||
using AIStudio.Provider.Perplexity;
|
||||
using AIStudio.Provider.SelfHosted;
|
||||
using AIStudio.Provider.X;
|
||||
using AIStudio.Settings;
|
||||
@@ -38,6 +39,7 @@ public static class LLMProvidersExtensions
|
||||
LLMProviders.X => "xAI",
|
||||
LLMProviders.DEEP_SEEK => "DeepSeek",
|
||||
LLMProviders.ALIBABA_CLOUD => "Alibaba Cloud",
|
||||
LLMProviders.PERPLEXITY => "Perplexity",
|
||||
|
||||
LLMProviders.GROQ => "Groq",
|
||||
LLMProviders.FIREWORKS => "Fireworks.ai",
|
||||
@@ -86,6 +88,8 @@ public static class LLMProvidersExtensions
|
||||
LLMProviders.DEEP_SEEK => Confidence.CHINA_NO_TRAINING.WithRegion("Asia").WithSources("https://cdn.deepseek.com/policies/en-US/deepseek-open-platform-terms-of-service.html").WithLevel(settingsManager.GetConfiguredConfidenceLevel(llmProvider)),
|
||||
|
||||
LLMProviders.ALIBABA_CLOUD => Confidence.CHINA_NO_TRAINING.WithRegion("Asia").WithSources("https://www.alibabacloud.com/help/en/model-studio/support/faq-about-alibaba-cloud-model-studio").WithLevel(settingsManager.GetConfiguredConfidenceLevel(llmProvider)),
|
||||
|
||||
LLMProviders.PERPLEXITY => Confidence.USA_NO_TRAINING.WithRegion("America, U.S.").WithSources("https://www.perplexity.ai/hub/legal/perplexity-api-terms-of-service").WithLevel(settingsManager.GetConfiguredConfidenceLevel(llmProvider)),
|
||||
|
||||
LLMProviders.SELF_HOSTED => Confidence.SELF_HOSTED.WithLevel(settingsManager.GetConfiguredConfidenceLevel(llmProvider)),
|
||||
|
||||
@@ -121,6 +125,7 @@ public static class LLMProvidersExtensions
|
||||
LLMProviders.GWDG => false,
|
||||
LLMProviders.DEEP_SEEK => false,
|
||||
LLMProviders.HUGGINGFACE => false,
|
||||
LLMProviders.PERPLEXITY => false,
|
||||
|
||||
//
|
||||
// Self-hosted providers are treated as a special case anyway.
|
||||
@@ -165,6 +170,7 @@ public static class LLMProvidersExtensions
|
||||
LLMProviders.X => new ProviderX(logger) { InstanceName = instanceName },
|
||||
LLMProviders.DEEP_SEEK => new ProviderDeepSeek(logger) { InstanceName = instanceName },
|
||||
LLMProviders.ALIBABA_CLOUD => new ProviderAlibabaCloud(logger) { InstanceName = instanceName },
|
||||
LLMProviders.PERPLEXITY => new ProviderPerplexity(logger) { InstanceName = instanceName },
|
||||
|
||||
LLMProviders.GROQ => new ProviderGroq(logger) { InstanceName = instanceName },
|
||||
LLMProviders.FIREWORKS => new ProviderFireworks(logger) { InstanceName = instanceName },
|
||||
@@ -194,6 +200,7 @@ public static class LLMProvidersExtensions
|
||||
LLMProviders.X => "https://accounts.x.ai/sign-up",
|
||||
LLMProviders.DEEP_SEEK => "https://platform.deepseek.com/sign_up",
|
||||
LLMProviders.ALIBABA_CLOUD => "https://account.alibabacloud.com/register/intl_register.htm",
|
||||
LLMProviders.PERPLEXITY => "https://www.perplexity.ai/account/api",
|
||||
|
||||
LLMProviders.GROQ => "https://console.groq.com/",
|
||||
LLMProviders.FIREWORKS => "https://fireworks.ai/login",
|
||||
@@ -216,6 +223,7 @@ public static class LLMProvidersExtensions
|
||||
LLMProviders.FIREWORKS => "https://fireworks.ai/account/billing",
|
||||
LLMProviders.DEEP_SEEK => "https://platform.deepseek.com/usage",
|
||||
LLMProviders.ALIBABA_CLOUD => "https://usercenter2-intl.aliyun.com/billing",
|
||||
LLMProviders.PERPLEXITY => "https://www.perplexity.ai/account/api/",
|
||||
LLMProviders.HUGGINGFACE => "https://huggingface.co/settings/billing",
|
||||
|
||||
_ => string.Empty,
|
||||
@@ -232,6 +240,7 @@ public static class LLMProvidersExtensions
|
||||
LLMProviders.GOOGLE => true,
|
||||
LLMProviders.DEEP_SEEK => true,
|
||||
LLMProviders.ALIBABA_CLOUD => true,
|
||||
LLMProviders.PERPLEXITY => true,
|
||||
LLMProviders.HUGGINGFACE => true,
|
||||
|
||||
_ => false,
|
||||
@@ -278,6 +287,7 @@ public static class LLMProvidersExtensions
|
||||
LLMProviders.X => true,
|
||||
LLMProviders.DEEP_SEEK => true,
|
||||
LLMProviders.ALIBABA_CLOUD => true,
|
||||
LLMProviders.PERPLEXITY => true,
|
||||
|
||||
LLMProviders.GROQ => true,
|
||||
LLMProviders.FIREWORKS => true,
|
||||
@@ -299,6 +309,7 @@ public static class LLMProvidersExtensions
|
||||
LLMProviders.X => true,
|
||||
LLMProviders.DEEP_SEEK => true,
|
||||
LLMProviders.ALIBABA_CLOUD => true,
|
||||
LLMProviders.PERPLEXITY => true,
|
||||
|
||||
LLMProviders.GROQ => true,
|
||||
LLMProviders.FIREWORKS => true,
|
||||
|
||||
@@ -18,7 +18,7 @@ public sealed class ProviderMistral(ILogger logger) : BaseProvider("https://api.
|
||||
public override string InstanceName { get; set; } = "Mistral";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async IAsyncEnumerable<string> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
|
||||
public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
|
||||
@@ -19,7 +19,7 @@ public class NoProvider : IProvider
|
||||
|
||||
public Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default) => Task.FromResult<IEnumerable<Model>>([]);
|
||||
|
||||
public async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatChatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
|
||||
public async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatChatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
await Task.FromResult(0);
|
||||
yield break;
|
||||
|
||||
@@ -22,7 +22,7 @@ public sealed class ProviderOpenAI(ILogger logger) : BaseProvider("https://api.o
|
||||
public override string InstanceName { get; set; } = "OpenAI";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
|
||||
public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
|
||||
@@ -15,7 +15,7 @@ public readonly record struct ResponseStreamLine(string Id, string Object, uint
|
||||
public bool ContainsContent() => this != default && this.Choices.Count > 0;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetContent() => this.Choices[0].Delta.Content;
|
||||
public ContentStreamChunk GetContent() => new(this.Choices[0].Delta.Content, []);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
using System.Net.Http.Headers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Provider.OpenAI;
|
||||
using AIStudio.Settings;
|
||||
|
||||
namespace AIStudio.Provider.Perplexity;
|
||||
|
||||
public sealed class ProviderPerplexity(ILogger logger) : BaseProvider("https://api.perplexity.ai/", logger)
|
||||
{
|
||||
private static readonly Model[] KNOWN_MODELS =
|
||||
[
|
||||
new("sonar", "Sonar"),
|
||||
new("sonar-pro", "Sonar Pro"),
|
||||
new("sonar-reasoning", "Sonar Reasoning"),
|
||||
new("sonar-reasoning-pro", "Sonar Reasoning Pro"),
|
||||
new("sonar-deep-research", "Sonar Deep Research"),
|
||||
];
|
||||
|
||||
#region Implementation of IProvider
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string Id => LLMProviders.PERPLEXITY.ToName();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string InstanceName { get; set; } = "Perplexity";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
if(!requestedSecret.Success)
|
||||
yield break;
|
||||
|
||||
// Prepare the system prompt:
|
||||
var systemPrompt = new Message
|
||||
{
|
||||
Role = "system",
|
||||
Content = chatThread.PrepareSystemPrompt(settingsManager, chatThread, this.logger),
|
||||
};
|
||||
|
||||
// Prepare the Perplexity HTTP chat request:
|
||||
var perplexityChatRequest = JsonSerializer.Serialize(new ChatRequest
|
||||
{
|
||||
Model = chatModel.Id,
|
||||
|
||||
// Build the messages:
|
||||
// - First of all the system prompt
|
||||
// - Then none-empty user and AI messages
|
||||
Messages = [systemPrompt, ..chatThread.Blocks.Where(n => n.ContentType is ContentType.TEXT && !string.IsNullOrWhiteSpace((n.Content as ContentText)?.Text)).Select(n => new Message
|
||||
{
|
||||
Role = n.Role switch
|
||||
{
|
||||
ChatRole.USER => "user",
|
||||
ChatRole.AI => "assistant",
|
||||
ChatRole.AGENT => "assistant",
|
||||
ChatRole.SYSTEM => "system",
|
||||
|
||||
_ => "user",
|
||||
},
|
||||
|
||||
Content = n.Content switch
|
||||
{
|
||||
ContentText text => text.Text,
|
||||
_ => string.Empty,
|
||||
}
|
||||
}).ToList()],
|
||||
Stream = true,
|
||||
}, JSON_SERIALIZER_OPTIONS);
|
||||
|
||||
async Task<HttpRequestMessage> RequestBuilder()
|
||||
{
|
||||
// Build the HTTP post request:
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, "chat/completions");
|
||||
|
||||
// Set the authorization header:
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION));
|
||||
|
||||
// Set the content:
|
||||
request.Content = new StringContent(perplexityChatRequest, Encoding.UTF8, "application/json");
|
||||
return request;
|
||||
}
|
||||
|
||||
await foreach (var content in this.StreamChatCompletionInternal<ResponseStreamLine>("Perplexity", RequestBuilder, token))
|
||||
yield return content;
|
||||
}
|
||||
|
||||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
/// <inheritdoc />
|
||||
public override async IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
return this.LoadModels();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(Enumerable.Empty<Model>());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(Enumerable.Empty<Model>());
|
||||
}
|
||||
|
||||
public override IReadOnlyCollection<Capability> GetModelCapabilities(Model model)
|
||||
{
|
||||
var modelName = model.Id.ToLowerInvariant().AsSpan();
|
||||
|
||||
if(modelName.IndexOf("reasoning") is not -1 ||
|
||||
modelName.IndexOf("deep-research") is not -1)
|
||||
return
|
||||
[
|
||||
Capability.TEXT_INPUT,
|
||||
Capability.MULTIPLE_IMAGE_INPUT,
|
||||
|
||||
Capability.TEXT_OUTPUT,
|
||||
Capability.IMAGE_OUTPUT,
|
||||
|
||||
Capability.ALWAYS_REASONING,
|
||||
];
|
||||
|
||||
return
|
||||
[
|
||||
Capability.TEXT_INPUT,
|
||||
Capability.MULTIPLE_IMAGE_INPUT,
|
||||
|
||||
Capability.TEXT_OUTPUT,
|
||||
Capability.IMAGE_OUTPUT,
|
||||
];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private Task<IEnumerable<Model>> LoadModels() => Task.FromResult<IEnumerable<Model>>(KNOWN_MODELS);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
namespace AIStudio.Provider.Perplexity;
|
||||
|
||||
/// <summary>
|
||||
/// Data model for a line in the response stream, for streaming completions.
|
||||
/// </summary>
|
||||
/// <param name="Id">The id of the response.</param>
|
||||
/// <param name="Object">The object describing the response.</param>
|
||||
/// <param name="Created">The timestamp of the response.</param>
|
||||
/// <param name="Model">The model used for the response.</param>
|
||||
/// <param name="SystemFingerprint">The system fingerprint; together with the seed, this allows you to reproduce the response.</param>
|
||||
/// <param name="Choices">The choices made by the AI.</param>
|
||||
public readonly record struct ResponseStreamLine(string Id, string Object, uint Created, string Model, string SystemFingerprint, IList<Choice> Choices, IList<SearchResult> SearchResults) : IResponseStreamLine
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public bool ContainsContent() => this != default && this.Choices.Count > 0;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ContentStreamChunk GetContent() => new(this.Choices[0].Delta.Content, this.GetSources());
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ContainsSources() => this != default && this.SearchResults.Count > 0;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IList<ISource> GetSources() => this.SearchResults.Cast<ISource>().ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data model for a choice made by the AI.
|
||||
/// </summary>
|
||||
/// <param name="Index">The index of the choice.</param>
|
||||
/// <param name="Delta">The delta text of the choice.</param>
|
||||
public readonly record struct Choice(int Index, Delta Delta);
|
||||
|
||||
/// <summary>
|
||||
/// The delta text of a choice.
|
||||
/// </summary>
|
||||
/// <param name="Content">The content of the delta text.</param>
|
||||
public readonly record struct Delta(string Content);
|
||||
|
||||
/// <summary>
|
||||
/// Data model for a search result.
|
||||
/// </summary>
|
||||
/// <param name="Title">The title of the search result.</param>
|
||||
/// <param name="URL">The URL of the search result.</param>
|
||||
public sealed record SearchResult(string Title, string URL) : Source(Title, URL);
|
||||
@@ -18,7 +18,7 @@ public sealed class ProviderSelfHosted(ILogger logger, Host host, string hostnam
|
||||
public override string InstanceName { get; set; } = "Self-hosted";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async IAsyncEnumerable<string> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
|
||||
public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this, isTrying: true);
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace AIStudio.Provider;
|
||||
|
||||
/// <summary>
|
||||
/// Data model for a source used in the response.
|
||||
/// </summary>
|
||||
/// <param name="Title">The title of the source.</param>
|
||||
/// <param name="URL">The URL of the source.</param>
|
||||
public record Source(string Title, string URL) : ISource;
|
||||
@@ -0,0 +1,47 @@
|
||||
using System.Text;
|
||||
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
|
||||
namespace AIStudio.Provider;
|
||||
|
||||
public static class SourceExtensions
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(SourceExtensions).Namespace, nameof(SourceExtensions));
|
||||
|
||||
/// <summary>
|
||||
/// Converts a list of sources to a markdown-formatted string.
|
||||
/// </summary>
|
||||
/// <param name="sources">The list of sources to convert.</param>
|
||||
/// <returns>A markdown-formatted string representing the sources.</returns>
|
||||
public static string ToMarkdown(this IList<Source> sources)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.Append("## ");
|
||||
sb.AppendLine(TB("Sources"));
|
||||
|
||||
var sourceNum = 0;
|
||||
foreach (var source in sources)
|
||||
{
|
||||
sb.Append($"- [{++sourceNum}] ");
|
||||
sb.Append('[');
|
||||
sb.Append(source.Title);
|
||||
sb.Append("](");
|
||||
sb.Append(source.URL);
|
||||
sb.AppendLine(")");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges a list of added sources into an existing list of sources, avoiding duplicates based on URL and Title.
|
||||
/// </summary>
|
||||
/// <param name="sources">The existing list of sources to merge into.</param>
|
||||
/// <param name="addedSources">The list of sources to add.</param>
|
||||
public static void MergeSources(this IList<Source> sources, IList<ISource> addedSources)
|
||||
{
|
||||
foreach (var addedSource in addedSources)
|
||||
if (sources.All(s => s.URL != addedSource.URL && s.Title != addedSource.Title))
|
||||
sources.Add((Source)addedSource);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ public sealed class ProviderX(ILogger logger) : BaseProvider("https://api.x.ai/v
|
||||
public override string InstanceName { get; set; } = "xAI";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async IAsyncEnumerable<string> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
|
||||
public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
|
||||
Reference in new issue
Block a user