FunctionCalling

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
using var api = GetAuthenticatedClient();

var service = new FunctionCallingService();
IList<ChatCompletionTool> tools = service.AsTools().AsOpenAiTools();

AssistantObject assistant = await api.Assistants.CreateAssistantAsync(
    model: CreateAssistantRequestModel.Gpt4o,
    name: "Example: Function Calling",
    instructions: "Don't make assumptions about what values to plug into functions."
                  + " Ask for clarification if a user request is ambiguous.",
    tools: tools.Select(x => new ToolsItem2(new AssistantToolsFunction
    {
        Function = x.Function,
    })).ToArray());

RunObject run = await api.Assistants.CreateThreadAndRunAsync(
    assistantId: assistant.Id,
    new CreateThreadRequest
    {
        Messages = ["What's the weather like today?"],
    });

// Poll the run until it is no longer queued or in progress.
while (run.Status is RunObjectStatus.Queued or RunObjectStatus.InProgress or RunObjectStatus.RequiresAction)
{
    await Task.Delay(TimeSpan.FromSeconds(1));
    run = await api.Assistants.GetRunAsync(run.ThreadId, run.Id);

    // If the run requires action, resolve them.
    if (run.Status == RunObjectStatus.RequiresAction)
    {
        List<SubmitToolOutputsRunRequestToolOutput> toolOutputs = [];

        foreach (RunToolCallObject toolCall in run.RequiredAction?.SubmitToolOutputs.ToolCalls ?? [])
        {
            var json = await service.CallAsync(
                functionName: toolCall.Function.Name,
                argumentsAsJson: toolCall.Function.Arguments);
            toolOutputs.Add(new SubmitToolOutputsRunRequestToolOutput
            {
                ToolCallId = toolCall.Id,
                Output = json,
            });
        }

        // Submit the tool outputs to the assistant, which returns the run to the queued state.
        run = await api.Assistants.SubmitToolOuputsToRunAsync(
            threadId: run.ThreadId,
            runId: run.Id,
            toolOutputs: toolOutputs);
    }
}

// With the run complete, list the messages and display their content
if (run.Status == RunObjectStatus.Completed)
{
    ListMessagesResponse messages
        = await api.Assistants.ListMessagesAsync(run.ThreadId, order: ListMessagesOrder.Asc);

    foreach (MessageObject message in messages.Data)
    {
        Console.WriteLine($"[{message.Role.ToString().ToUpper()}]: ");
        foreach (ContentItem2 contentItem in message.Content)
        {
            if (contentItem.ImageFile is {} imageFile)
            {
                Console.WriteLine($" <Image File ID> {imageFile.ImageFile.FileId}");
            }
            if (contentItem.ImageUrl is {} imageUrl)
            {
                Console.WriteLine($" <Image URL> {imageUrl.ImageUrl.Url}");
            }
            if (contentItem.Text is {} text)
            {
                Console.WriteLine($"{text.Text.Value}");

                // Include annotations, if any.
                if (text.Text.Annotations.Count > 0)
                {
                    Console.WriteLine();
                    foreach (AnnotationsItem annotation in text.Text.Annotations)
                    {
                        if (annotation.FileCitation is {} fileCitation)
                        {
                            Console.WriteLine($"* File citation, file ID: {fileCitation.FileCitation.FileId}");
                            Console.WriteLine($"* Text to replace: {fileCitation.Text}");
                            Console.WriteLine($"* Message content index range: {fileCitation.StartIndex}-{fileCitation.EndIndex}");
                        }
                        if (annotation.FilePath is {} filePath)
                        {
                            Console.WriteLine($"* File output, new file ID: {filePath.FilePath.FileId}");
                            Console.WriteLine($"* Text to replace: {filePath.Text}");
                            Console.WriteLine($"* Message content index range: {filePath.StartIndex}-{filePath.EndIndex}");
                        }
                    }
                }
            }
            if (contentItem.Refusal is {} refusal)
            {
                Console.WriteLine($"Refusal: {refusal.Refusal}");
            }
        }
        Console.WriteLine();
    }
}
else
{
    throw new NotImplementedException(run.Status.ToString());
}

// Optionally, delete any persistent resources you no longer need.
_ = await api.Assistants.DeleteThreadAsync(run.ThreadId);
_ = await api.Assistants.DeleteAssistantAsync(assistant.Id);