semantic-kernel
semantic-kernel copied to clipboard
Request function return type to be generic
I'm implementing Cognitive Search Skill and Function and encountered below issue.
I have 15 index on Azure Search that I don't wanna write functions for each index.
Tried to return SearchResults<T> but this just didn't go through function invoke.
It just returned context passed in.
below is my prototype
- ISearchConnector.cs
// Copyright (c) Microsoft. All rights reserved.
using System.Threading.Tasks;
using Microsoft.SemanticKernel.Orchestration;
namespace Microsoft.SemanticKernel.Skills.Search;
/// <summary>
/// Search connector interface.
/// </summary>
public interface ISearchConnector<T>
{
/// <summary>
/// Search with given query.
/// </summary>
/// <param name="query"></param>
/// <param name="context"></param>
/// <returns></returns>
public T Search(string query, SKContext context);
/// <summary>
/// Search with given query.
/// </summary>
/// <param name="query"></param>
/// <param name="context"></param>
/// <returns></returns>
public Task<T> SearchAsync(string query, SKContext context);
}
- SearchConnector.cs
// Copyright (c) Microsoft. All rights reserved.
using Azure.Search.Documents.Models;
using Azure.Search.Documents;
using Azure;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Microsoft.SemanticKernel.Orchestration;
using Microsoft.SemanticKernel.Skills.Search;
namespace SemanticKernel.Service.Model;
public class SearchConnector<T> : ISearchConnector<SearchResults<T>>
{
private readonly CognitiveSearchConfig _cognitiveSearchConfig;
private readonly ILogger<SearchConnector<T>> _logger;
public SearchConnector(IOptions<CognitiveSearchConfig> cognitiveSearchConfig
, ILogger<SearchConnector<T>>? logger = null
)
{
this._cognitiveSearchConfig = cognitiveSearchConfig.Value;
this._logger = logger ?? NullLogger<SearchConnector<T>>.Instance;
}
public SearchResults<T> Search(string query, SKContext context)
{
var searchClient = this.CreateSearchClient(context.Variables);
var searchOptions = this.CreateSearchOptions(context.Variables);
var response = searchClient.Search<T>($"{query}", searchOptions);
var result = response.Value;
this._logger.LogDebug("{0} search result(s) recieved", result.TotalCount);
return result;
}
public async Task<SearchResults<T>> SearchAsync(string query, SKContext context)
{
var searchClient = this.CreateSearchClient(context.Variables);
var searchOptions = this.CreateSearchOptions(context.Variables);
var response = await searchClient.SearchAsync<T>($"{query}", searchOptions, context.CancellationToken);
var result = response.Value;
this._logger.LogDebug("{0} search result(s) recieved", result.TotalCount);
return result;
}
private SearchClient CreateSearchClient(ContextVariables contextVariables)
{
var indexName = contextVariables["indexName"];
var endPoint = new Uri($"https://{this._cognitiveSearchConfig.ServiceName}.search.windows.net/");
var credential = new AzureKeyCredential(this._cognitiveSearchConfig.ServiceQueryApiKey);
var client = new SearchClient(endPoint, indexName, credential);
this._logger.LogDebug("SearchClient created with indexName: {0}", indexName);
return client;
}
private SearchOptions CreateSearchOptions(ContextVariables context)
{
context.Get("includeTotalCount", out string includeTotalCountOut);
return new SearchOptions
{
IncludeTotalCount = bool.TryParse(includeTotalCountOut, out bool includeTotalCount),
};
}
}
- SearchSkill.cs
// Copyright (c) Microsoft. All rights reserved.
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.SemanticKernel.Orchestration;
using Microsoft.SemanticKernel.SkillDefinition;
namespace Microsoft.SemanticKernel.Skills.Search;
public class SearchSkill<T>
{
private readonly IKernel _kernel;
private readonly ISearchConnector<T> _searchConnector;
private readonly ILogger _logger;
public SearchSkill(IKernel kernel, ISearchConnector<T> searchConnector, ILogger<SearchSkill<T>> logger)
{
this._kernel = kernel;
this._searchConnector = searchConnector;
this._logger = logger ?? NullLogger<SearchSkill<T>>.Instance;
}
/// <summary>
/// Search the query using the cognitive search.
/// </summary>
/// <param name="query"></param>
/// <param name="context"></param>
/// <returns></returns>
[SKFunction("Perform a cognitive search.")]
[SKFunctionName("Search")]
[SKFunctionInput(Description = "Text to search for")]
[SKFunctionContextParameter(Name = "indexName", Description = "IndexName of CognitiveSearch")]
public async Task<T> SearchAsync(string query, SKContext context)
{
this._logger.LogDebug("Request search query: {0}", query);
var documents = await this._searchConnector.SearchAsync(query, context);
return documents;
}
}
Calling the function
SKContext result = await kernel.RunAsync(contextVariables, function!);
the result of function call is just reply of my input
Result of call
{"value":"my search query","variables":[{"key":"indexName","value":"gptkbindex"},{"key":"INPUT","value":"my search query"},{"key":"audience","value":"hyunoo"}]}
I'm thinking that I could possibly chain this function to another function that takes object if SKFunction could return object with type(generic would be better)
Please correct me if i'm using this library wrong.