✨ Multi model support
This commit is contained in:
@@ -2,6 +2,7 @@ using System.ComponentModel.DataAnnotations;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using DysonNetwork.Shared.Auth;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -19,12 +20,45 @@ public class ThoughtController(ThoughtProvider provider, ThoughtService service)
|
||||
public class StreamThinkingRequest
|
||||
{
|
||||
[Required] public string UserMessage { get; set; } = null!;
|
||||
public string? ServiceId { get; set; }
|
||||
public Guid? SequenceId { get; set; }
|
||||
public List<string>? AttachedPosts { get; set; }
|
||||
public List<Dictionary<string, dynamic>>? AttachedMessages { get; set; }
|
||||
public List<string> AcceptProposals { get; set; } = [];
|
||||
}
|
||||
|
||||
public class ThoughtServiceInfo
|
||||
{
|
||||
public string ServiceId { get; set; } = null!;
|
||||
public double BillingMultiplier { get; set; }
|
||||
public int PerkLevel { get; set; }
|
||||
}
|
||||
|
||||
public class ThoughtServicesResponse
|
||||
{
|
||||
public string DefaultService { get; set; } = null!;
|
||||
public IEnumerable<ThoughtServiceInfo> Services { get; set; } = null!;
|
||||
}
|
||||
|
||||
[HttpGet("services")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public ActionResult<ThoughtServicesResponse> GetAvailableServices()
|
||||
{
|
||||
var services = provider.GetAvailableServicesInfo()
|
||||
.Select(s => new ThoughtServiceInfo
|
||||
{
|
||||
ServiceId = s.ServiceId,
|
||||
BillingMultiplier = s.BillingMultiplier,
|
||||
PerkLevel = s.PerkLevel
|
||||
});
|
||||
|
||||
return Ok(new ThoughtServicesResponse
|
||||
{
|
||||
DefaultService = provider.GetDefaultServiceId(),
|
||||
Services = services
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Experimental("SKEXP0110")]
|
||||
public async Task<ActionResult> Think([FromBody] StreamThinkingRequest request)
|
||||
@@ -35,6 +69,26 @@ public class ThoughtController(ThoughtProvider provider, ThoughtService service)
|
||||
if (request.AcceptProposals.Any(e => !AvailableProposals.Contains(e)))
|
||||
return BadRequest("Request contains unavailable proposal");
|
||||
|
||||
var serviceId = provider.GetServiceId(request.ServiceId);
|
||||
var serviceInfo = provider.GetServiceInfo(serviceId);
|
||||
if (serviceInfo is null)
|
||||
{
|
||||
return BadRequest("Service not found or configured.");
|
||||
}
|
||||
|
||||
// TODO: Check perk level from `currentUser`
|
||||
if (serviceInfo.PerkLevel > 0 && !currentUser.IsSuperuser)
|
||||
if (currentUser.PerkSubscription is null ||
|
||||
PerkSubscriptionPrivilege.GetPrivilegeFromIdentifier(currentUser.PerkSubscription.Identifier) <
|
||||
serviceInfo.PerkLevel)
|
||||
return StatusCode(403, "Not enough perk level");
|
||||
|
||||
var kernel = provider.GetKernel(request.ServiceId);
|
||||
if (kernel is null)
|
||||
{
|
||||
return BadRequest("Service not found or configured.");
|
||||
}
|
||||
|
||||
// Generate a topic if creating a new sequence
|
||||
string? topic = null;
|
||||
if (!request.SequenceId.HasValue)
|
||||
@@ -46,7 +100,13 @@ public class ThoughtController(ThoughtProvider provider, ThoughtService service)
|
||||
);
|
||||
summaryHistory.AddUserMessage(request.UserMessage);
|
||||
|
||||
var summaryResult = await provider.Kernel
|
||||
var summaryKernel = provider.GetKernel(); // Get default kernel
|
||||
if (summaryKernel is null)
|
||||
{
|
||||
return BadRequest("Default service not found or configured.");
|
||||
}
|
||||
|
||||
var summaryResult = await summaryKernel
|
||||
.GetRequiredService<IChatCompletionService>()
|
||||
.GetChatMessageContentAsync(summaryHistory);
|
||||
|
||||
@@ -58,14 +118,13 @@ public class ThoughtController(ThoughtProvider provider, ThoughtService service)
|
||||
if (sequence == null) return Forbid(); // or NotFound
|
||||
|
||||
// Save user thought
|
||||
await service.SaveThoughtAsync(sequence, new List<SnThinkingMessagePart>
|
||||
{
|
||||
new()
|
||||
await service.SaveThoughtAsync(sequence, [
|
||||
new SnThinkingMessagePart
|
||||
{
|
||||
Type = ThinkingMessagePartType.Text,
|
||||
Text = request.UserMessage
|
||||
}
|
||||
}, ThinkingThoughtRole.User);
|
||||
], ThinkingThoughtRole.User);
|
||||
|
||||
// Build chat history
|
||||
var chatHistory = new ChatHistory(
|
||||
@@ -172,15 +231,13 @@ public class ThoughtController(ThoughtProvider provider, ThoughtService service)
|
||||
|
||||
chatHistory.Add(assistantMessage);
|
||||
|
||||
if (functionResults.Count > 0)
|
||||
{
|
||||
if (functionResults.Count <= 0) continue;
|
||||
foreach (var fr in functionResults)
|
||||
{
|
||||
chatHistory.Add(fr.ToChatMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
chatHistory.AddUserMessage(request.UserMessage);
|
||||
|
||||
@@ -188,9 +245,8 @@ public class ThoughtController(ThoughtProvider provider, ThoughtService service)
|
||||
Response.Headers.Append("Content-Type", "text/event-stream");
|
||||
Response.StatusCode = 200;
|
||||
|
||||
var kernel = provider.Kernel;
|
||||
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
|
||||
var executionSettings = provider.CreatePromptExecutionSettings();
|
||||
var executionSettings = provider.CreatePromptExecutionSettings(request.ServiceId);
|
||||
|
||||
var assistantParts = new List<SnThinkingMessagePart>();
|
||||
|
||||
@@ -300,7 +356,7 @@ public class ThoughtController(ThoughtProvider provider, ThoughtService service)
|
||||
sequence,
|
||||
assistantParts,
|
||||
ThinkingThoughtRole.Assistant,
|
||||
provider.ModelDefault
|
||||
serviceId
|
||||
);
|
||||
|
||||
// Write the topic if it was newly set, then the thought object as JSON to the stream
|
||||
|
||||
@@ -13,6 +13,15 @@ using Microsoft.SemanticKernel.Plugins.Web.Google;
|
||||
|
||||
namespace DysonNetwork.Insight.Thought;
|
||||
|
||||
public class ThoughtServiceModel
|
||||
{
|
||||
public string ServiceId { get; set; } = null!;
|
||||
public string? Provider { get; set; }
|
||||
public string? Model { get; set; }
|
||||
public double BillingMultiplier { get; set; }
|
||||
public int PerkLevel { get; set; }
|
||||
}
|
||||
|
||||
public class ThoughtProvider
|
||||
{
|
||||
private readonly PostService.PostServiceClient _postClient;
|
||||
@@ -20,11 +29,10 @@ public class ThoughtProvider
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ILogger<ThoughtProvider> _logger;
|
||||
|
||||
public Kernel Kernel { get; }
|
||||
|
||||
private string? ModelProviderType { get; set; }
|
||||
public string? ModelDefault { get; set; }
|
||||
public List<string> ModelAvailable { get; set; } = [];
|
||||
private readonly Dictionary<string, Kernel> _kernels = new();
|
||||
private readonly Dictionary<string, string> _serviceProviders = new();
|
||||
private readonly Dictionary<string, ThoughtServiceModel> _serviceModels = new();
|
||||
private readonly string _defaultServiceId;
|
||||
|
||||
[Experimental("SKEXP0050")]
|
||||
public ThoughtProvider(
|
||||
@@ -39,29 +47,48 @@ public class ThoughtProvider
|
||||
_accountClient = accountServiceClient;
|
||||
_configuration = configuration;
|
||||
|
||||
Kernel = InitializeThinkingProvider(configuration);
|
||||
InitializeHelperFunctions();
|
||||
var cfg = configuration.GetSection("Thinking");
|
||||
_defaultServiceId = cfg.GetValue<string>("DefaultService")!;
|
||||
var services = cfg.GetSection("Services").GetChildren();
|
||||
|
||||
foreach (var service in services)
|
||||
{
|
||||
var serviceId = service.Key;
|
||||
var serviceModel = new ThoughtServiceModel
|
||||
{
|
||||
ServiceId = serviceId,
|
||||
Provider = service.GetValue<string>("Provider"),
|
||||
Model = service.GetValue<string>("Model"),
|
||||
BillingMultiplier = service.GetValue<double>("BillingMultiplier", 1.0),
|
||||
PerkLevel = service.GetValue<int>("PerkLevel", 0)
|
||||
};
|
||||
_serviceModels[serviceId] = serviceModel;
|
||||
|
||||
var providerType = service.GetValue<string>("Provider")?.ToLower();
|
||||
if (providerType is null) continue;
|
||||
|
||||
var kernel = InitializeThinkingService(service);
|
||||
InitializeHelperFunctions(kernel);
|
||||
_kernels[serviceId] = kernel;
|
||||
_serviceProviders[serviceId] = providerType;
|
||||
}
|
||||
}
|
||||
|
||||
private Kernel InitializeThinkingProvider(IConfiguration configuration)
|
||||
private Kernel InitializeThinkingService(IConfigurationSection serviceConfig)
|
||||
{
|
||||
var cfg = configuration.GetSection("Thinking");
|
||||
ModelProviderType = cfg.GetValue<string>("Provider")?.ToLower();
|
||||
ModelDefault = cfg.GetValue<string>("Model");
|
||||
ModelAvailable = cfg.GetValue<List<string>>("ModelAvailable") ?? [];
|
||||
var endpoint = cfg.GetValue<string>("Endpoint");
|
||||
var apiKey = cfg.GetValue<string>("ApiKey");
|
||||
var providerType = serviceConfig.GetValue<string>("Provider")?.ToLower();
|
||||
var model = serviceConfig.GetValue<string>("Model");
|
||||
var endpoint = serviceConfig.GetValue<string>("Endpoint");
|
||||
var apiKey = serviceConfig.GetValue<string>("ApiKey");
|
||||
|
||||
var builder = Kernel.CreateBuilder();
|
||||
|
||||
switch (ModelProviderType)
|
||||
switch (providerType)
|
||||
{
|
||||
case "ollama":
|
||||
foreach (var model in ModelAvailable)
|
||||
builder.AddOllamaChatCompletion(
|
||||
ModelDefault!,
|
||||
new Uri(endpoint ?? "http://localhost:11434/api"),
|
||||
model
|
||||
model!,
|
||||
new Uri(endpoint ?? "http://localhost:11434/api")
|
||||
);
|
||||
break;
|
||||
case "deepseek":
|
||||
@@ -69,11 +96,10 @@ public class ThoughtProvider
|
||||
new ApiKeyCredential(apiKey!),
|
||||
new OpenAIClientOptions { Endpoint = new Uri(endpoint ?? "https://api.deepseek.com/v1") }
|
||||
);
|
||||
foreach (var model in ModelAvailable)
|
||||
builder.AddOpenAIChatCompletion(ModelDefault!, client, model);
|
||||
builder.AddOpenAIChatCompletion(model!, client);
|
||||
break;
|
||||
default:
|
||||
throw new IndexOutOfRangeException("Unknown thinking provider: " + ModelProviderType);
|
||||
throw new IndexOutOfRangeException("Unknown thinking provider: " + providerType);
|
||||
}
|
||||
|
||||
// Add gRPC clients for Thought Plugins
|
||||
@@ -89,7 +115,7 @@ public class ThoughtProvider
|
||||
}
|
||||
|
||||
[Experimental("SKEXP0050")]
|
||||
private void InitializeHelperFunctions()
|
||||
private void InitializeHelperFunctions(Kernel kernel)
|
||||
{
|
||||
// Add web search plugins if configured
|
||||
var bingApiKey = _configuration.GetValue<string>("Thinking:BingApiKey");
|
||||
@@ -97,7 +123,7 @@ public class ThoughtProvider
|
||||
{
|
||||
var bingConnector = new BingConnector(bingApiKey);
|
||||
var bing = new WebSearchEnginePlugin(bingConnector);
|
||||
Kernel.ImportPluginFromObject(bing, "bing");
|
||||
kernel.ImportPluginFromObject(bing, "bing");
|
||||
}
|
||||
|
||||
var googleApiKey = _configuration.GetValue<string>("Thinking:GoogleApiKey");
|
||||
@@ -108,26 +134,58 @@ public class ThoughtProvider
|
||||
apiKey: googleApiKey,
|
||||
searchEngineId: googleCx);
|
||||
var google = new WebSearchEnginePlugin(googleConnector);
|
||||
Kernel.ImportPluginFromObject(google, "google");
|
||||
kernel.ImportPluginFromObject(google, "google");
|
||||
}
|
||||
}
|
||||
|
||||
public PromptExecutionSettings CreatePromptExecutionSettings()
|
||||
public Kernel? GetKernel(string? serviceId = null)
|
||||
{
|
||||
switch (ModelProviderType)
|
||||
serviceId ??= _defaultServiceId;
|
||||
return _kernels.GetValueOrDefault(serviceId);
|
||||
}
|
||||
|
||||
public string GetServiceId(string? serviceId = null)
|
||||
{
|
||||
case "ollama":
|
||||
return new OllamaPromptExecutionSettings
|
||||
return serviceId ?? _defaultServiceId;
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetAvailableServices()
|
||||
{
|
||||
return _kernels.Keys;
|
||||
}
|
||||
|
||||
public IEnumerable<ThoughtServiceModel> GetAvailableServicesInfo()
|
||||
{
|
||||
return _serviceModels.Values;
|
||||
}
|
||||
|
||||
public ThoughtServiceModel? GetServiceInfo(string? serviceId)
|
||||
{
|
||||
serviceId ??= _defaultServiceId;
|
||||
return _serviceModels.GetValueOrDefault(serviceId);
|
||||
}
|
||||
|
||||
public string GetDefaultServiceId()
|
||||
{
|
||||
return _defaultServiceId;
|
||||
}
|
||||
|
||||
public PromptExecutionSettings CreatePromptExecutionSettings(string? serviceId = null)
|
||||
{
|
||||
serviceId ??= _defaultServiceId;
|
||||
var providerType = _serviceProviders.GetValueOrDefault(serviceId);
|
||||
|
||||
return providerType switch
|
||||
{
|
||||
"ollama" => new OllamaPromptExecutionSettings
|
||||
{
|
||||
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(autoInvoke: false)
|
||||
};
|
||||
case "deepseek":
|
||||
return new OpenAIPromptExecutionSettings
|
||||
},
|
||||
"deepseek" => new OpenAIPromptExecutionSettings
|
||||
{
|
||||
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(autoInvoke: false)
|
||||
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(autoInvoke: false), ModelId = serviceId
|
||||
},
|
||||
_ => throw new InvalidOperationException("Unknown provider for service: " + serviceId)
|
||||
};
|
||||
default:
|
||||
throw new InvalidOperationException("Unknown provider: " + ModelProviderType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,22 @@
|
||||
"Insecure": true
|
||||
},
|
||||
"Thinking": {
|
||||
"DefaultService": "deepseek-chat",
|
||||
"Services": {
|
||||
"deepseek-chat": {
|
||||
"Provider": "deepseek",
|
||||
"Model": "deepseek-chat",
|
||||
"ModelAvailable": ["deepseek-chat", "deepseek-reasoner"],
|
||||
"ApiKey": "sk-bd20f6a2e9fa40b98c46899baa0e9f09"
|
||||
"ApiKey": "sk-",
|
||||
"BillingMultiplier": 1.0,
|
||||
"PerkLevel": 0
|
||||
},
|
||||
"deepseek-reasoner": {
|
||||
"Provider": "deepseek",
|
||||
"Model": "deepseek-reasoner",
|
||||
"ApiKey": "sk-",
|
||||
"BillingMultiplier": 1.5,
|
||||
"PerkLevel": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user