openai / openai-dotnet

The official .NET library for the OpenAI API
https://www.nuget.org/packages/OpenAI
MIT License
707 stars 60 forks source link

Unable to mock CompleteChatAsync method #89

Open risogolo opened 5 days ago

risogolo commented 5 days ago

How can I mock response from CompleteChatAsync method? using 2.0.0-beta.3 Content property in ChatCompletion cannot be overriden is that's even possible, can you please provide an example

BlackGad commented 5 days ago

Also stream version of method.

BlackGad commented 5 days ago

@risogolo If you dont use streaming version here is temporary solution for CompleteChatAsync

public static class OpenAIMockFactory
{
    public static ChatTokenUsage ChatTokenUsage(int outputTokens, int inputTokens, int totalTokens)
    {
        var type = typeof(ChatTokenUsage);
        //internal ChatTokenUsage(int outputTokens, int inputTokens, int totalTokens)
        var instance = Activator.CreateInstance(
            type,
            BindingFlags.NonPublic | BindingFlags.Instance,
            null,
            [
                outputTokens, // outputTokens
                inputTokens,  // inputTokens
                totalTokens,  // totalTokens
            ],
            null);
        return (ChatTokenUsage) instance;
    }

    public static ChatCompletion ChatCompletion(
        string id = null,
        string model = null,
        DateTimeOffset? createdAt = null,
        ChatFunctionCall chatFunctionCall = null,
        ChatToolCall chatToolCall = null,
        IEnumerable<ChatMessageContentPart> contentParts = null,
        ChatTokenUsage usage = null)
    {
        var type = typeof(ChatCompletion);
        var types = type.Assembly.GetTypes();

        var chatToolCalls = new List<ChatToolCall>();
        if (chatToolCall != null)
        {
            chatToolCalls.Add(chatToolCall);
        }

        var contentPartsList = new List<ChatMessageContentPart>();
        if (contentParts != null)
        {
            contentPartsList.AddRange(contentParts);
        }

        //internal InternalChatCompletionResponseMessage(IReadOnlyList<ChatMessageContentPart> content, IReadOnlyList<ChatToolCall> toolCalls, ChatMessageRole role, ChatFunctionCall functionCall, IDictionary<string, BinaryData> serializedAdditionalRawData)
        var internalChatCompletionResponseMessageType = types.Single(x => x.Name == "InternalChatCompletionResponseMessage");
        var internalChatCompletionResponseMessage = Activator.CreateInstance(
            internalChatCompletionResponseMessageType,
            BindingFlags.NonPublic | BindingFlags.Instance,
            null,
            [
                contentPartsList,          // content
                chatToolCalls,             // toolCalls
                ChatMessageRole.Assistant, // role
                chatFunctionCall,          // functionCall
                null                       // serializedAdditionalRawData
            ],
            null);

        //internal InternalCreateChatCompletionResponseChoice(ChatFinishReason finishReason, int index, InternalChatCompletionResponseMessage message, InternalCreateChatCompletionResponseChoiceLogprobs logprobs)
        var internalCreateChatCompletionResponseChoiceType = types.Single(x => x.Name == "InternalCreateChatCompletionResponseChoice");
        var internalCreateChatCompletionResponseChoice = Activator.CreateInstance(
            internalCreateChatCompletionResponseChoiceType,
            BindingFlags.NonPublic | BindingFlags.Instance,
            null,
            [
                ChatFinishReason.Stop,                 // finishReason
                0,                                     // index
                internalChatCompletionResponseMessage, // message
                null                                   // logprobs
            ],
            null);

        //internal ChatCompletion(string id, IReadOnlyList<InternalCreateChatCompletionResponseChoice> choices, DateTimeOffset createdAt, string model, string systemFingerprint, InternalCreateChatCompletionResponseObject @object, ChatTokenUsage usage, IDictionary<string, BinaryData> serializedAdditionalRawData)
        var array = Array.CreateInstance(internalCreateChatCompletionResponseChoiceType, 1);
        array.SetValue(internalCreateChatCompletionResponseChoice, 0);
        var instance = Activator.CreateInstance(
            type,
            BindingFlags.NonPublic | BindingFlags.Instance,
            null,
            [
                id ?? Guid.NewGuid().ToString(), // id
                array,                           // choices
                createdAt ?? DateTimeOffset.Now, // createdAt
                model ?? "model",                // model
                Guid.NewGuid().ToString(),       // systemFingerprint
                null,                            // object
                usage,                           // usage
                null                             // serializedAdditionalRawData
            ],
            null);

        return (ChatCompletion) instance;
    }
}
risogolo commented 5 days ago

@BlackGad thank you, your solution works, but I still think the lib should support much more easier approach how to mock ChatCompletion, for example with usage of any standard mocking library instead of using reflection

BlackGad commented 5 days ago

@BlackGad thank you, your solution works, but I still think the lib should support much more easier approach how to mock ChatCompletion with usage of any standard mocking library

No doubt. This is just TEMPORARY solution :)