♻️ Refactored the thought Solar Network related plugins
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
using DysonNetwork.Insight.Thought;
|
||||||
using Quartz;
|
using Quartz;
|
||||||
|
|
||||||
namespace DysonNetwork.Insight.Startup;
|
namespace DysonNetwork.Insight.Startup;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System.Text.Json;
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using DysonNetwork.Insight.Thought;
|
using DysonNetwork.Insight.Thought;
|
||||||
using DysonNetwork.Shared.Cache;
|
using DysonNetwork.Shared.Cache;
|
||||||
|
using DysonNetwork.Shared.Registry;
|
||||||
using Microsoft.SemanticKernel;
|
using Microsoft.SemanticKernel;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
using NodaTime.Serialization.SystemTextJson;
|
using NodaTime.Serialization.SystemTextJson;
|
||||||
@@ -63,17 +64,13 @@ public static class ServiceCollectionExtensions
|
|||||||
|
|
||||||
public static IServiceCollection AddThinkingServices(this IServiceCollection services, IConfiguration configuration)
|
public static IServiceCollection AddThinkingServices(this IServiceCollection services, IConfiguration configuration)
|
||||||
{
|
{
|
||||||
|
// Add gRPC clients for ThoughtService
|
||||||
|
services.AddSphereService();
|
||||||
|
services.AddAccountService();
|
||||||
|
|
||||||
services.AddSingleton<ThoughtProvider>();
|
services.AddSingleton<ThoughtProvider>();
|
||||||
services.AddScoped<ThoughtService>();
|
services.AddScoped<ThoughtService>();
|
||||||
|
|
||||||
// Add gRPC clients for ThoughtService
|
|
||||||
services.AddGrpcClient<Shared.Proto.PaymentService.PaymentServiceClient>(o => o.Address = new Uri("https://_grpc.pass"))
|
|
||||||
.ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
|
|
||||||
{ ServerCertificateCustomValidationCallback = (_, _, _, _) => true });
|
|
||||||
services.AddGrpcClient<Shared.Proto.WalletService.WalletServiceClient>(o => o.Address = new Uri("https://_grpc.pass"))
|
|
||||||
.ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
|
|
||||||
{ ServerCertificateCustomValidationCallback = (_, _, _, _) => true });
|
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using DysonNetwork.Shared.Models;
|
||||||
|
using DysonNetwork.Shared.Proto;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using Microsoft.SemanticKernel;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Insight.Thought.Plugins;
|
||||||
|
|
||||||
|
public class SnAccountKernelPlugin(
|
||||||
|
AccountService.AccountServiceClient accountClient
|
||||||
|
)
|
||||||
|
{
|
||||||
|
[KernelFunction("get_account")]
|
||||||
|
public async Task<SnAccount?> GetAccount(string userId)
|
||||||
|
{
|
||||||
|
var request = new GetAccountRequest { Id = userId };
|
||||||
|
var response = await accountClient.GetAccountAsync(request);
|
||||||
|
if (response is null) return null;
|
||||||
|
return SnAccount.FromProtoValue(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
[KernelFunction("get_account_by_name")]
|
||||||
|
public async Task<SnAccount?> GetAccountByName(string username)
|
||||||
|
{
|
||||||
|
var request = new LookupAccountBatchRequest();
|
||||||
|
request.Names.Add(username);
|
||||||
|
var response = await accountClient.LookupAccountBatchAsync(request);
|
||||||
|
return response.Accounts.IsNullOrEmpty() ? null : SnAccount.FromProtoValue(response.Accounts[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
98
DysonNetwork.Insight/Thought/Plugins/SnPostKernelPlugin.cs
Normal file
98
DysonNetwork.Insight/Thought/Plugins/SnPostKernelPlugin.cs
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using DysonNetwork.Shared.Models;
|
||||||
|
using DysonNetwork.Shared.Proto;
|
||||||
|
using Microsoft.SemanticKernel;
|
||||||
|
using NodaTime;
|
||||||
|
using NodaTime.Serialization.Protobuf;
|
||||||
|
using NodaTime.Text;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Insight.Thought.Plugins;
|
||||||
|
|
||||||
|
public class SnPostKernelPlugin(
|
||||||
|
PostService.PostServiceClient postClient
|
||||||
|
)
|
||||||
|
{
|
||||||
|
[KernelFunction("get_post")]
|
||||||
|
public async Task<SnPost?> GetPost(string postId)
|
||||||
|
{
|
||||||
|
var request = new GetPostRequest { Id = postId };
|
||||||
|
var response = await postClient.GetPostAsync(request);
|
||||||
|
return response is null ? null : SnPost.FromProtoValue(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
[KernelFunction("search_posts")]
|
||||||
|
[Description("Perform a full-text search in all Solar Network posts.")]
|
||||||
|
public async Task<List<SnPost>> SearchPostsContent(string contentQuery, int pageSize = 10, int page = 1)
|
||||||
|
{
|
||||||
|
var request = new SearchPostsRequest
|
||||||
|
{
|
||||||
|
Query = contentQuery,
|
||||||
|
PageSize = pageSize,
|
||||||
|
PageToken = ((page - 1) * pageSize).ToString()
|
||||||
|
};
|
||||||
|
var response = await postClient.SearchPostsAsync(request);
|
||||||
|
return response.Posts.Select(SnPost.FromProtoValue).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class KernelPostListResult
|
||||||
|
{
|
||||||
|
public List<SnPost> Posts { get; set; } = [];
|
||||||
|
public int TotalCount { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[KernelFunction("list_posts")]
|
||||||
|
[Description("List all posts on the Solar Network without filters, orderBy can be date or popularity")]
|
||||||
|
public async Task<KernelPostListResult> ListPosts(
|
||||||
|
string orderBy = "date",
|
||||||
|
bool orderDesc = true,
|
||||||
|
int pageSize = 10,
|
||||||
|
int page = 1
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var request = new ListPostsRequest
|
||||||
|
{
|
||||||
|
OrderBy = orderBy,
|
||||||
|
OrderDesc = orderDesc,
|
||||||
|
PageSize = pageSize,
|
||||||
|
PageToken = ((page - 1) * pageSize).ToString()
|
||||||
|
};
|
||||||
|
var response = await postClient.ListPostsAsync(request);
|
||||||
|
return new KernelPostListResult
|
||||||
|
{
|
||||||
|
Posts = response.Posts.Select(SnPost.FromProtoValue).ToList(),
|
||||||
|
TotalCount = response.TotalSize,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[KernelFunction("list_posts_within_time")]
|
||||||
|
[Description(
|
||||||
|
"List posts in a period of time, the time requires ISO-8601 format, one of the start and end must be provided.")]
|
||||||
|
public async Task<KernelPostListResult> ListPostsWithinTime(
|
||||||
|
string? beforeTime,
|
||||||
|
string? afterTime,
|
||||||
|
int pageSize = 10,
|
||||||
|
int page = 1
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var pattern = InstantPattern.General;
|
||||||
|
Instant? before = !string.IsNullOrWhiteSpace(beforeTime)
|
||||||
|
? pattern.Parse(beforeTime).TryGetValue(default, out var beforeValue) ? beforeValue : null
|
||||||
|
: null;
|
||||||
|
Instant? after = !string.IsNullOrWhiteSpace(afterTime)
|
||||||
|
? pattern.Parse(afterTime).TryGetValue(default, out var afterValue) ? afterValue : null
|
||||||
|
: null;
|
||||||
|
var request = new ListPostsRequest
|
||||||
|
{
|
||||||
|
After = after?.ToTimestamp(),
|
||||||
|
Before = before?.ToTimestamp(),
|
||||||
|
PageSize = pageSize,
|
||||||
|
PageToken = ((page - 1) * pageSize).ToString()
|
||||||
|
};
|
||||||
|
var response = await postClient.ListPostsAsync(request);
|
||||||
|
return new KernelPostListResult
|
||||||
|
{
|
||||||
|
Posts = response.Posts.Select(SnPost.FromProtoValue).ToList(),
|
||||||
|
TotalCount = response.TotalSize,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.ClientModel;
|
using System.ClientModel;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using DysonNetwork.Insight.Thought.Plugins;
|
||||||
using DysonNetwork.Shared.Models;
|
using DysonNetwork.Shared.Models;
|
||||||
using DysonNetwork.Shared.Proto;
|
using DysonNetwork.Shared.Proto;
|
||||||
using Microsoft.SemanticKernel;
|
using Microsoft.SemanticKernel;
|
||||||
@@ -71,81 +72,15 @@ public class ThoughtProvider
|
|||||||
throw new IndexOutOfRangeException("Unknown thinking provider: " + ModelProviderType);
|
throw new IndexOutOfRangeException("Unknown thinking provider: " + ModelProviderType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
builder.Plugins.AddFromType<SnAccountKernelPlugin>();
|
||||||
|
builder.Plugins.AddFromType<SnPostKernelPlugin>();
|
||||||
|
|
||||||
return builder.Build();
|
return builder.Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Experimental("SKEXP0050")]
|
[Experimental("SKEXP0050")]
|
||||||
private void InitializeHelperFunctions()
|
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
|
// Add web search plugins if configured
|
||||||
var bingApiKey = _configuration.GetValue<string>("Thinking:BingApiKey");
|
var bingApiKey = _configuration.GetValue<string>("Thinking:BingApiKey");
|
||||||
if (!string.IsNullOrEmpty(bingApiKey))
|
if (!string.IsNullOrEmpty(bingApiKey))
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using DysonNetwork.Insight.Thought;
|
|
||||||
using Quartz;
|
using Quartz;
|
||||||
|
|
||||||
namespace DysonNetwork.Insight.Startup;
|
namespace DysonNetwork.Insight.Thought;
|
||||||
|
|
||||||
public class TokenBillingJob(ThoughtService thoughtService, ILogger<TokenBillingJob> logger) : IJob
|
public class TokenBillingJob(ThoughtService thoughtService, ILogger<TokenBillingJob> logger) : IJob
|
||||||
{
|
{
|
||||||
@@ -243,6 +243,7 @@ message ListPostsRequest {
|
|||||||
int32 page_size = 3;
|
int32 page_size = 3;
|
||||||
string page_token = 4;
|
string page_token = 4;
|
||||||
google.protobuf.StringValue order_by = 5;
|
google.protobuf.StringValue order_by = 5;
|
||||||
|
bool order_desc = 16;
|
||||||
repeated string categories = 6;
|
repeated string categories = 6;
|
||||||
repeated string tags = 7;
|
repeated string tags = 7;
|
||||||
google.protobuf.StringValue query = 8;
|
google.protobuf.StringValue query = 8;
|
||||||
|
|||||||
@@ -125,13 +125,22 @@ public class PostServiceGrpc(AppDatabase db, PostService ps) : Shared.Proto.Post
|
|||||||
.Include(p => p.FeaturedRecords)
|
.Include(p => p.FeaturedRecords)
|
||||||
.AsQueryable();
|
.AsQueryable();
|
||||||
|
|
||||||
query = request.Shuffle
|
if (request.Shuffle)
|
||||||
? query.OrderBy(e => EF.Functions.Random())
|
|
||||||
: request.OrderBy switch
|
|
||||||
{
|
{
|
||||||
"asc" => query.OrderBy(e => e.PublishedAt ?? e.CreatedAt),
|
query = query.OrderBy(e => EF.Functions.Random());
|
||||||
_ => query.OrderByDescending(e => e.PublishedAt ?? e.CreatedAt),
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
query = request.OrderBy switch
|
||||||
|
{
|
||||||
|
"popularity" => request.OrderDesc
|
||||||
|
? query.OrderByDescending(e => e.Upvotes * 10 - e.Downvotes * 10 + e.AwardedScore)
|
||||||
|
: query.OrderBy(e => e.Upvotes * 10 - e.Downvotes * 10 + e.AwardedScore),
|
||||||
|
_ => request.OrderDesc
|
||||||
|
? query.OrderByDescending(e => e.PublishedAt ?? e.CreatedAt)
|
||||||
|
: query.OrderBy(e => e.PublishedAt ?? e.CreatedAt)
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(request.PublisherId) && Guid.TryParse(request.PublisherId, out var pid))
|
if (!string.IsNullOrWhiteSpace(request.PublisherId) && Guid.TryParse(request.PublisherId, out var pid))
|
||||||
query = query.Where(p => p.PublisherId == pid);
|
query = query.Where(p => p.PublisherId == pid);
|
||||||
|
|||||||
Reference in New Issue
Block a user