Azure / azure-sdk-for-net

This repository is for active development of the Azure SDK for .NET. For consumers of the SDK we recommend visiting our public developer docs at https://learn.microsoft.com/dotnet/azure/ or our versioned developer docs at https://azure.github.io/azure-sdk-for-net.
MIT License
5.37k stars 4.78k forks source link

[FEATURE REQ] Use source-generator to support chat function with type #38924

Open LittleLittleCloud opened 1 year ago

LittleLittleCloud commented 1 year ago

Library name

Azure.AI.OpenAI

Please describe the feature.

To leverage typed method into Chat Function, we can use source generator to generate FunctionDefinition and a wrapper function, which takes in a string as input, retrieve arguments from that string and mapping back to original typed method.

This can also reduce the effort of consuming function call with GPT, where a FunctionDefinition object needs to be manually crafted, arguments need to be manually parsed and the two steps need to be synchronized for every change.

Both FunctionDefinition and wrapper function can be generated according to syntax information like function name, argument type and its comments if there's any.

Here's one implementation https://github.com/LittleLittleCloud/groupchat-example-for-dotnet/tree/main/src/GroupChatExample.SourceGenerator

Example

// file: ChatFunction.cs
public partial class ChatFunction
{
    /// <summary>
    /// Get the current weather in a given location.
    /// </summary>
    /// <param name="city">The city to get the weather for</param>
    /// <param name="date">The date to get the weather for</param>
    [FunctionAttribution]
    public string GetCurrentWeather(string city, string date)
    {
        return $"The weather in {city} on {date} will be sunny.";
    }
}

We can make use of its function name, parameter list and structured xml comment, and generate FunctionDefinition and it's wrapping function for consumption. This can save the effort of creating function definition object and keep parsing code in place with function parameters.

// file: ChatFunction.generated.cs
public partial class ChatFunction
{
    public FunctionDefinition GetCurrentWeatherFunction
    {
        get => {
            new FunctionDefinition()
            {
                Name = "GetCurrentWeather",
                Description = "Get the current weather in a given location.",
// todo
// using literal Json string 
                Parameters = BinaryData.FromObjectAsJson(
                new
                {
                    Type = "object",
                    Properties = new
                    {
                        city = new
                        {
                            Type = "string",
                            Description = "The city to get the weather for",
                        },
                        date = new
                        {
                            Type = "string",
                            Description = "The date to get the weather for",
                        }
                    },
                },
                new JsonSerializerOptions() {  PropertyNamingPolicy = JsonNamingPolicy.CamelCase }),
            };
        }
    }

    private class GetCurrentWeatherSchema
    {
        public string city { get; set; }
        public string date { get; set; }
    }

    public string GetCurrentWeather(string arguments)
    {
        var schema = JsonSerializer.Deserialize<GetCurrentWeatherSchema>(arguments);
        return GetCurrentWeather(schema.city, schema.date);
    }
}

Nice to have

vbandi commented 1 year ago

I created something similar for another openai lib, although using reflection instead of source generation https://www.linkedin.com/pulse/using-openai-function-calling-net-andr%C3%A1s-velv%C3%A1rt/

It even goes one step further by streamlining the actual function calling if the AI so requires.

I'd love to contribute by implementing it for Azure SDK.

vbandi commented 1 year ago

I got this working for Azure OpenAI SDK:

completionOptions.Functions = FunctionCallingHelper.GetFunctionDefinitions<Calculator>();
...
var functionCall = firstChoice.Message.FunctionCall;
var functionCallResult = FunctionCallingHelper.CallFunction<float>(functionCall, calculator);
...

public class Calculator
{
    [FunctionDescription("Adds two numbers.")]
    public float Add(float a, float b)
    {
        return a + b;
    }

    [FunctionDescription("Subtracts two numbers.")]
    public float Subtract(float a, float b)
    {
        return a - b;
    }

Please let me know, and I'll prepare a PR.

LittleLittleCloud commented 1 year ago

Just find another similar issue from dotnet-interactive

https://github.com/dotnet/interactive/issues/3243

@jsquire Any action plan for that? Generally, we can make FunctionCall much easier in C# and it would be something really useful given the fact that there are already three wheels implemented to facilitate FunctionCall under this thread

jsquire commented 1 year ago

@LittleLittleCloud: I have no insight. We'll need an update from the OpenAI team.

LittleLittleCloud commented 1 year ago

@jsquire Thanks, Could you please loop OpenAI SDK team under this thread. also @trrwilson, any suggestions/feedback for supporting function_all with type? I already have an implementation and I can create a draft PR if you like.