198 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			198 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
using System.ClientModel;
 | 
						|
using System.Diagnostics.CodeAnalysis;
 | 
						|
using System.Text.Json;
 | 
						|
using DysonNetwork.Shared.Models;
 | 
						|
using DysonNetwork.Shared.Proto;
 | 
						|
using Microsoft.SemanticKernel;
 | 
						|
using Microsoft.SemanticKernel.Connectors.Ollama;
 | 
						|
using Microsoft.SemanticKernel.Connectors.OpenAI;
 | 
						|
using OpenAI;
 | 
						|
using PostType = DysonNetwork.Shared.Proto.PostType;
 | 
						|
using Microsoft.SemanticKernel.Plugins.Web;
 | 
						|
using Microsoft.SemanticKernel.Plugins.Web.Bing;
 | 
						|
using Microsoft.SemanticKernel.Plugins.Web.Google;
 | 
						|
using NodaTime.Serialization.Protobuf;
 | 
						|
using NodaTime.Text;
 | 
						|
 | 
						|
namespace DysonNetwork.Insight.Thought;
 | 
						|
 | 
						|
public class ThoughtProvider
 | 
						|
{
 | 
						|
    private readonly PostService.PostServiceClient _postClient;
 | 
						|
    private readonly AccountService.AccountServiceClient _accountClient;
 | 
						|
    private readonly IConfiguration _configuration;
 | 
						|
    private readonly ILogger<ThoughtProvider> _logger;
 | 
						|
 | 
						|
    public Kernel Kernel { get; }
 | 
						|
 | 
						|
    private string? ModelProviderType { get; set; }
 | 
						|
    public string? ModelDefault { get; set; }
 | 
						|
 | 
						|
    [Experimental("SKEXP0050")]
 | 
						|
    public ThoughtProvider(
 | 
						|
        IConfiguration configuration,
 | 
						|
        PostService.PostServiceClient postServiceClient,
 | 
						|
        AccountService.AccountServiceClient accountServiceClient,
 | 
						|
        ILogger<ThoughtProvider> logger
 | 
						|
    )
 | 
						|
    {
 | 
						|
        _logger = logger;
 | 
						|
        _postClient = postServiceClient;
 | 
						|
        _accountClient = accountServiceClient;
 | 
						|
        _configuration = configuration;
 | 
						|
 | 
						|
        Kernel = InitializeThinkingProvider(configuration);
 | 
						|
        InitializeHelperFunctions();
 | 
						|
    }
 | 
						|
 | 
						|
    private Kernel InitializeThinkingProvider(IConfiguration configuration)
 | 
						|
    {
 | 
						|
        var cfg = configuration.GetSection("Thinking");
 | 
						|
        ModelProviderType = cfg.GetValue<string>("Provider")?.ToLower();
 | 
						|
        ModelDefault = cfg.GetValue<string>("Model");
 | 
						|
        var endpoint = cfg.GetValue<string>("Endpoint");
 | 
						|
        var apiKey = cfg.GetValue<string>("ApiKey");
 | 
						|
 | 
						|
        var builder = Kernel.CreateBuilder();
 | 
						|
 | 
						|
        switch (ModelProviderType)
 | 
						|
        {
 | 
						|
            case "ollama":
 | 
						|
                builder.AddOllamaChatCompletion(ModelDefault!, new Uri(endpoint ?? "http://localhost:11434/api"));
 | 
						|
                break;
 | 
						|
            case "deepseek":
 | 
						|
                var client = new OpenAIClient(
 | 
						|
                    new ApiKeyCredential(apiKey!),
 | 
						|
                    new OpenAIClientOptions { Endpoint = new Uri(endpoint ?? "https://api.deepseek.com/v1") }
 | 
						|
                );
 | 
						|
                builder.AddOpenAIChatCompletion(ModelDefault!, client);
 | 
						|
                break;
 | 
						|
            default:
 | 
						|
                throw new IndexOutOfRangeException("Unknown thinking provider: " + ModelProviderType);
 | 
						|
        }
 | 
						|
 | 
						|
        return builder.Build();
 | 
						|
    }
 | 
						|
 | 
						|
    [Experimental("SKEXP0050")]
 | 
						|
    private void InitializeHelperFunctions()
 | 
						|
    {
 | 
						|
        // Add Solar Network tools plugin
 | 
						|
        Kernel.ImportPluginFromFunctions("solar_network", [
 | 
						|
            KernelFunctionFactory.CreateFromMethod(async (string userId) =>
 | 
						|
            {
 | 
						|
                var request = new GetAccountRequest { Id = userId };
 | 
						|
                var response = await _accountClient.GetAccountAsync(request);
 | 
						|
                return JsonSerializer.Serialize(response, GrpcTypeHelper.SerializerOptions);
 | 
						|
            }, "get_user", "Get a user profile from the Solar Network."),
 | 
						|
            KernelFunctionFactory.CreateFromMethod(async (string postId) =>
 | 
						|
            {
 | 
						|
                var request = new GetPostRequest { Id = postId };
 | 
						|
                var response = await _postClient.GetPostAsync(request);
 | 
						|
                return JsonSerializer.Serialize(response, GrpcTypeHelper.SerializerOptions);
 | 
						|
            }, "get_post", "Get a single post by ID from the Solar Network."),
 | 
						|
            KernelFunctionFactory.CreateFromMethod(async (string query) =>
 | 
						|
                {
 | 
						|
                    var request = new SearchPostsRequest { Query = query, PageSize = 10 };
 | 
						|
                    var response = await _postClient.SearchPostsAsync(request);
 | 
						|
                    return JsonSerializer.Serialize(response.Posts, GrpcTypeHelper.SerializerOptions);
 | 
						|
                }, "search_posts",
 | 
						|
                "Search posts by query from the Solar Network. The input query is will be used to search with title, description and body content"),
 | 
						|
            KernelFunctionFactory.CreateFromMethod(async (
 | 
						|
                    string? orderBy = null,
 | 
						|
                    string? afterIso = null,
 | 
						|
                    string? beforeIso = null
 | 
						|
                ) =>
 | 
						|
                {
 | 
						|
                    _logger.LogInformation("Begin building request to list post from sphere...");
 | 
						|
 | 
						|
                    var request = new ListPostsRequest
 | 
						|
                    {
 | 
						|
                        PageSize = 20,
 | 
						|
                        OrderBy = orderBy,
 | 
						|
                    };
 | 
						|
                    if (!string.IsNullOrEmpty(afterIso))
 | 
						|
                        try
 | 
						|
                        {
 | 
						|
                            request.After = InstantPattern.General.Parse(afterIso).Value.ToTimestamp();
 | 
						|
                        }
 | 
						|
                        catch (Exception)
 | 
						|
                        {
 | 
						|
                            _logger.LogWarning("Invalid afterIso format: {AfterIso}", afterIso);
 | 
						|
                        }
 | 
						|
                    if (!string.IsNullOrEmpty(beforeIso))
 | 
						|
                        try
 | 
						|
                        {
 | 
						|
                            request.Before = InstantPattern.General.Parse(beforeIso).Value.ToTimestamp();
 | 
						|
                        }
 | 
						|
                        catch (Exception)
 | 
						|
                        {
 | 
						|
                            _logger.LogWarning("Invalid beforeIso format: {BeforeIso}", beforeIso);
 | 
						|
                        }
 | 
						|
 | 
						|
                    _logger.LogInformation("Request built, {Request}", request);
 | 
						|
 | 
						|
                    var response = await _postClient.ListPostsAsync(request);
 | 
						|
 | 
						|
                    var data = response.Posts.Select(SnPost.FromProtoValue);
 | 
						|
                    _logger.LogInformation("Sphere service returned posts: {Posts}", data);
 | 
						|
                    return JsonSerializer.Serialize(data, GrpcTypeHelper.SerializerOptions);
 | 
						|
                }, "list_posts",
 | 
						|
                "Get posts from the Solar Network.\n" +
 | 
						|
                "Parameters:\n" +
 | 
						|
                "orderBy (optional, string: order by published date, accept asc or desc)\n" +
 | 
						|
                "afterIso (optional, string: ISO date for posts after this date)\n" +
 | 
						|
                "beforeIso (optional, string: ISO date for posts before this date)"
 | 
						|
            )
 | 
						|
        ]);
 | 
						|
 | 
						|
        // Add web search plugins if configured
 | 
						|
        var bingApiKey = _configuration.GetValue<string>("Thinking:BingApiKey");
 | 
						|
        if (!string.IsNullOrEmpty(bingApiKey))
 | 
						|
        {
 | 
						|
            var bingConnector = new BingConnector(bingApiKey);
 | 
						|
            var bing = new WebSearchEnginePlugin(bingConnector);
 | 
						|
            Kernel.ImportPluginFromObject(bing, "bing");
 | 
						|
        }
 | 
						|
 | 
						|
        var googleApiKey = _configuration.GetValue<string>("Thinking:GoogleApiKey");
 | 
						|
        var googleCx = _configuration.GetValue<string>("Thinking:GoogleCx");
 | 
						|
        if (!string.IsNullOrEmpty(googleApiKey) && !string.IsNullOrEmpty(googleCx))
 | 
						|
        {
 | 
						|
            var googleConnector = new GoogleConnector(
 | 
						|
                apiKey: googleApiKey,
 | 
						|
                searchEngineId: googleCx);
 | 
						|
            var google = new WebSearchEnginePlugin(googleConnector);
 | 
						|
            Kernel.ImportPluginFromObject(google, "google");
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    public PromptExecutionSettings CreatePromptExecutionSettings()
 | 
						|
    {
 | 
						|
        switch (ModelProviderType)
 | 
						|
        {
 | 
						|
            case "ollama":
 | 
						|
                return new OllamaPromptExecutionSettings
 | 
						|
                {
 | 
						|
                    FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(
 | 
						|
                        options: new FunctionChoiceBehaviorOptions
 | 
						|
                        {
 | 
						|
                            AllowParallelCalls = true,
 | 
						|
                            AllowConcurrentInvocation = true
 | 
						|
                        })
 | 
						|
                };
 | 
						|
            case "deepseek":
 | 
						|
                return new OpenAIPromptExecutionSettings
 | 
						|
                {
 | 
						|
                    FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(
 | 
						|
                        options: new FunctionChoiceBehaviorOptions
 | 
						|
                        {
 | 
						|
                            AllowParallelCalls = true,
 | 
						|
                            AllowConcurrentInvocation = true
 | 
						|
                        })
 | 
						|
                };
 | 
						|
            default:
 | 
						|
                throw new InvalidOperationException("Unknown provider: " + ModelProviderType);
 | 
						|
        }
 | 
						|
    }
 | 
						|
} |