semantic-kernel icon indicating copy to clipboard operation
semantic-kernel copied to clipboard

Request function return type to be generic

Open hyunoosung opened this issue 2 years ago • 0 comments

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.

hyunoosung avatar Apr 15 '23 13:04 hyunoosung