> ## Content Index
> Fetch the complete content index at: https://www.azurebrasil.cloud/llms.txt
> Use this file to discover other available public pages before exploring further.

# Utilizando function calling nos assistentes Azure OpenAI
- URL: https://www.azurebrasil.cloud/blog/utilizando-function-calling-nos-assistentes-azure-openai/
- Published: 2024-07-17T12:47:22.000Z
- Updated: 2025-07-25T17:15:25.000Z
- Author: Talles Valiatti

O [Azure OpenAI](https://learn.microsoft.com/pt-br/azure/ai-services/openai/overview?ref=azurebrasil.cloud) é um serviço da Microsoft que integra modelos avançados de inteligência artificial, como o [GPT-4o](https://learn.microsoft.com/pt-br/azure/ai-services/openai/concepts/models?ref=azurebrasil.cloud), em diversas aplicações. Ele oferece funcionalidades como geração de texto, tradução de idiomas, análise de sentimentos e resumo de textos, aprimorando a automação e eficiência de soluções empresariais. Neste tutorial, vamos configurar uma [web API](https://learn.microsoft.com/pt-br/aspnet/core/tutorials/first-web-api?view=aspnetcore-8.0&tabs=visual-studio&ref=azurebrasil.cloud) em .NET que utiliza o serviço Azure OpenAI com a funcionalidade de [function calling](https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/function-calling?ref=azurebrasil.cloud).

O [function calling](https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/function-calling?ref=azurebrasil.cloud) da OpenAI é uma funcionalidade que permite a modelos de linguagem executarem funções específicas durante uma conversa. Em vez de apenas gerar texto, o modelo pode reconhecer quando uma ação ou operação é necessária e chamar uma função predefinida para realizar essa tarefa. Isso pode incluir consultar dados, executar cálculos, acessar APIs externas ou manipular informações de forma mais estruturada. A capacidade de chamar funções aumenta a utilidade prática dos modelos de linguagem, permitindo a integração com sistemas complexos e proporcionando respostas mais precisas e contextualizadas.

Vamos ao projeto! Começaremos criando uma pasta raiz para nossa solução.

```
mkdir AzureOpenAiFunctionCalling

```

Em seguida, vamos criar o arquivo `.gitignore` para nossa solução:

```
dotnet new gitignore
```

Para nosso projeto, vamos criar uma [Web API](https://learn.microsoft.com/pt-br/aspnet/core/tutorials/first-web-api?view=aspnetcore-8.0&tabs=visual-studio&ref=azurebrasil.cloud) que será o coração da nossa aplicação:

```
dotnet new webapi
```

Agora, vamos instalar o pacote NuGet [Azure.AI.OpenAI](https://www.nuget.org/packages/Azure.AI.OpenAI?ref=azurebrasil.cloud) que nos permitirá integrar o serviço Azure OpenAI:

```
dotnet add package Azure.AI.OpenAI --prerelease 

```

Vamos criar um arquivo para configurar as [*IOptions*](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-8.0&ref=azurebrasil.cloud) do Azure OpenAI. Crie um novo arquivo e adicione o seguinte código:

```
namespace AzureOpenAiFunctionCalling.AzureOpenAi;

public class AzureOpenAiConfig
{
    public string Endpoint { get; set; } = default!;
    public string Key { get; set; } = default!;
    public string Model { get; set; } = default!;
}
```

Adicione as chaves necessárias no arquivo `appsettings.json`:

```
  "AzureOpenAi": {
    "Endpoint": "<your-endpoint>",
    "Key" : "<your-key>",
    "Model" : "<your-model>"
  }
```

Adicione a configuração no arquivo `Program.cs` para que nossa aplicação utilize as opções do Azure OpenAI:

```
builder.Services.Configure<AzureOpenAiConfig>(builder.Configuration.GetSection("AzureOpenAi"));
```

Agora vamos criar um record para representar os assistentes na pasta `Models`:

```
namespace AzureOpenAiFunctionCalling.AzureOpenAi.Models;

public record Assistants(string Id, string Name, string Instructions);
```

Crie outro [record](https://learn.microsoft.com/pt-br/dotnet/csharp/language-reference/builtin-types/record?ref=azurebrasil.cloud) para as requisições de criação de assistentes:

```
public record CreateAssistantRequest(string Name, string Description);
```

Defina a interface do serviço que gerenciará os assistentes do Azure Open AI:

```
public interface IAssistantService
{
    Task<IList<Assistants>> ListAssistantAsync();
    Task<Assistants?> GetAssistantsByIdAsync(string id);
    Task<Assistants> CreateAssistantAsync(CreateAssistantRequest request);
}
```

Implemente o serviço que gerenciará os assistentes:

```
public partial class AssistantService(IOptions<AzureOpenAiConfig> config) : IAssistantService
{
    private readonly AzureOpenAiConfig _azureOpenAiConfig = config.Value;
    
    private AzureOpenAIClient GetClient()
    {
        return new AzureOpenAIClient(
            new Uri(_azureOpenAiConfig.Endpoint), 
            new AzureKeyCredential(_azureOpenAiConfig.Key));
    }
}
```

```csharp
#pragma warning disable OPENAI001
public partial class AssistantService
{
    public async Task<IList<Assistants>> ListAssistantAsync()
    {
        var client = GetClient();
        var assistantClient = client.GetAssistantClient();

         AsyncPageableCollection<Assistant> assistants = assistantClient.GetAssistantsAsync();

         var result = new List<Assistants>();
         await foreach (Assistant assistant in assistants)
         {
             result.Add(new Assistants(
                 assistant.Id, 
                 assistant.Name,
                 assistant.Instructions));
         }

         return result;
    }
}
#pragma warning restore OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
```

```
#pragma warning disable OPENAI001
public partial class AssistantService
{
    public async Task<Assistants?> GetAssistantsByIdAsync(string id)
    {
        try
        {
            var client = GetClient();
            var assistantClient = client.GetAssistantClient();

            var assistant = await assistantClient.GetAssistantAsync(id);

            return new Assistants(
                assistant.Value.Id, 
                assistant.Value.Name,
                assistant.Value.Instructions);
        }
        catch (ClientResultException ex) when (ex.Status == 404)
        {
            return null;
        }
    }
}
#pragma warning restore OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
```

```
#pragma warning disable OPENAI001
public partial class AssistantService
{
    public async Task<Assistants> CreateAssistantAsync(CreateAssistantRequest request)
    {
        var client = GetClient();
        var assistantClient = client.GetAssistantClient();
        
        var assistantOptions = new AssistantCreationOptions
        {
            Name = request.Name,
            Instructions = request.Description,
            ToolResources = new()
        };

        Assistant assistant = await assistantClient.CreateAssistantAsync(_azureOpenAiConfig.Model, assistantOptions);

        return new Assistants(
            assistant.Id, 
            assistant.Name,
            assistant.Instructions);
    }
}
#pragma warning restore OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
```

Observe que utilizamos [*partial classes*](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/partial-classes-and-methods?ref=azurebrasil.cloud)para a implementação acima. Um arquivo para cada método!

Configure a [injeção de dependência](https://learn.microsoft.com/pt-br/dotnet/core/extensions/dependency-injection?ref=azurebrasil.cloud) para o serviço criado anteriormente:

```
builder.Services.AddScoped<IAssistantService, AssistantService>();
```

Configure os endpoints para os métodos implementados no `Program.cs`:

```
app.MapGet("/assistants", async (IAssistantService service) => 
        Results.Ok(await service.ListAssistantAsync()))
.WithName("ListAssistants")
.WithOpenApi();

app.MapGet("/assistants/{id}", async (string id, IAssistantService service) => 
        Results.Ok(await service.GetAssistantsByIdAsync(id)))
    .WithName("GetAssistantsById")
    .WithOpenApi();

app.MapPost("/assistants", async (CreateAssistantRequest request, IAssistantService service) => 
        Results.Ok(await service.CreateAssistantAsync(request)))
    .WithName("CreateAssistant")
    .WithOpenApi();
```

Aqui está um exemplo de chamada para o endpoint de criação de assistentes:

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2024/07/Screen-Shot-2024-07-12-at-09.59.01.png)

Aqui está um exemplo de chamada para o endpoint de listagem de assistentes por ID:

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2024/07/Screen-Shot-2024-07-12-at-09.59.37.png)

Vamos começar a implementação das [*threads*](https://learn.microsoft.com/en-us/azure/ai-services/openai/assistants-reference-threads?tabs=python&ref=azurebrasil.cloud)*!*

Iniciaremos criando o record *ThreadResponse.*

```
namespace AzureOpenAiFunctionCalling.AzureOpenAi.Models;

public record ThreadResponse(string Id);
```

Adicione o método `CreateThreadAsync` na interface do serviço:

```
Task<ThreadResponse> CreateThreadAsync();
```

```
#pragma warning disable OPENAI001
public partial class AssistantService
{
    public async Task<ThreadResponse> CreateThreadAsync()
    {
        var client = GetClient();
        var assistantClient = client.GetAssistantClient();
        
        var thread = await assistantClient.CreateThreadAsync();

        return new ThreadResponse(thread.Value.Id);
    }
}
#pragma warning restore OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
```

Com o novo método pronto, podemos criar o novo *endpoint* para criação de [*threads*](https://learn.microsoft.com/en-us/azure/ai-services/openai/assistants-reference-threads?tabs=python&ref=azurebrasil.cloud).

```
app.MapPost("/threads", async (IAssistantService service) => 
        Results.Ok(await service.CreateThreadAsync()))
    .WithName("CreateThread")
    .WithOpenApi();
```

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2024/07/Screen-Shot-2024-07-12-at-10.13.46.png)

Agora vamos definir o método`RunAsync` que irá processar as mensagens:

```
Task<string> RunAsync(RunRequest request);
```

```
#pragma warning disable OPENAI001
public partial class AssistantService
{
    public async Task<string> RunAsync(RunRequest request)
    {
        var client = GetClient();
        var assistantClient = client.GetAssistantClient();

        var thread = (await assistantClient.GetThreadAsync(request.ThreadId)).Value;
        var assistant = (await assistantClient.GetAssistantAsync(request.AssistantId)).Value;
        
        await assistantClient.CreateMessageAsync(thread.Id, [MessageContent.FromText(request.Message)]);
        
        ThreadRun threadRun = await assistantClient.CreateRunAsync(thread.Id, assistant.Id);
        
        do
        {
            await Task.Delay(TimeSpan.FromMilliseconds(100));
            threadRun = await assistantClient.GetRunAsync(thread.Id, threadRun.Id);
        }
        while (threadRun.Status == RunStatus.Queued || threadRun.Status == RunStatus.InProgress);
        
        AsyncPageableCollection<ThreadMessage> messagePage = assistantClient.GetMessagesAsync(thread.Id, ListOrder.NewestFirst);
        await using var enumerator = messagePage.GetAsyncEnumerator();
        var messageItem = await enumerator.MoveNextAsync() ? enumerator.Current : null;

        return messageItem?.Content.FirstOrDefault()?.Text ?? string.Empty;
    }
}
#pragma warning restore OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
```

Vamos configurar o endpoint para o método `RunAsync`:

```
app.MapPost("/run", async (RunRequest request, IAssistantService azureOpenApiService) => 
        Results.Ok(await azureOpenApiService.RunAsync(request)))
    .WithName("Run")
    .WithOpenApi();
```

Aqui está um exemplo de chamada para o endpoint `Run`:

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2024/07/Screen-Shot-2024-07-12-at-10.25.37.png)

Observe que enviamos o *threadId, o assistantId* e a mensagem que deverá ser colocada na conversa.

Temos a resposta do endpoint!

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2024/07/Screen-Shot-2024-07-12-at-10.26.14.png)

Vamos enviar outra mensagem:

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2024/07/Screen-Shot-2024-07-12-at-10.27.06.png)

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2024/07/Screen-Shot-2024-07-12-at-10.27.25.png)

Observe que para responder a segunda mensagem, ele utilizou o histórico de mensagens da *thread*.

Nosso *assistant* está funcionando perfeitamento. Agora é hora de adicionarmos algumas *tools* (*function calling*).

Vamos criar e implementar o serviço `ChatCompletions`:

```
public interface IChatCompletionsService
{
    string Complete(string systemMessage, string userMessage);
    string Complete(string userMessage);
}
```

```\
using Azure;
using Azure.AI.OpenAI;
using Microsoft.Extensions.Options;
using OpenAI.Chat;

namespace AzureOpenAiFunctionCalling.AzureOpenAi.Services.ChatCompletions;

public class ChatCompletionsService(IOptions<AzureOpenAiConfig> config) : IChatCompletionsService
{
    private AzureOpenAIClient GetClient()
    {
        return new AzureOpenAIClient(
            new Uri(config.Value.Endpoint), 
            new AzureKeyCredential(config.Value.Key));
    }
    
    public string Complete(string userMessage, string systemMessage)
    {
        var client = GetClient();
        var chatClient = client.GetChatClient(config.Value.Model);

        var msgs = new List<ChatMessage>();

        if (!string.IsNullOrWhiteSpace(systemMessage))
        {
            msgs.Add(new SystemChatMessage(systemMessage));
        }

        msgs.Add(new UserChatMessage(userMessage));
        
        var completion = chatClient.CompleteChat(msgs.ToArray());

        var result = completion.Value.Content[0].Text;

        return result;
    }

    public string Complete(string userMessage)
    {
        return Complete(userMessage, string.Empty);
    }
}
```

Iremos, agora, definir a interface *`ITool`*. Ela será utilizado por todas as ferramentas que serão utilizadas via *function calling.*

```
using OpenAI.Assistants;

namespace AzureOpenAiFunctionCalling.AzureOpenAi.Tools;

public interface ITool
{
    public static abstract string Name { get; }

    public static abstract FunctionToolDefinition Definition { get; }
}
```

Vamos implementar a primeira *tool,* que será especializada em escrever artigos sobre um tema qualquer (parâmetro) em um determinado número de linhas (outo parâmetro):

```
using System.Text.Json;
using AzureOpenAiFunctionCalling.AzureOpenAi.Services.ChatCompletions;
using OpenAI.Assistants;

namespace AzureOpenAiFunctionCalling.AzureOpenAi.Tools;

public class BlogArticleWriter : ITool
{
    private const string MessageTemplate = """
          Create a article about '{generalIdea}' in {numberOfWords} words.
         Use the '{title}' as title.
         Outputs only the article 

         -----

         Use the following schema:

         <title>
         
         <content>
         """;
    
    public static string Name => nameof(BlogArticleWriter);

    public static FunctionToolDefinition Definition => new(
        name: Name,
        description: "Write article for a personal tech blog",
        parameters: BinaryData.FromObjectAsJson(
            new
            {
                Type = "object",
                Properties = new
                {
                    Title = new
                    {
                        Type = "string",
                        Description = "The title of the article",
                    },
                    GeneralIdea = new
                    {
                        Type = "string",
                        Description = "The idea of the article",
                    },
                    NumberOfLines = new
                    {
                        Type = "integer",
                        Description = "The number of lines",
                    },
                },
                Required = new[] { "Title", "GeneralIdea", "NumberOfLines" },
            },
            new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));

    public string Execute(
        ILogger logger, 
        IChatCompletionsService chatCompletionsService, 
        string title, 
        string generalIdea, 
        int numberOfLines)
    {
        var formattedMsg = MessageTemplate
            .Replace("{generalIdea}", generalIdea)
            .Replace("{title}", title)
            .Replace("{numberOfLines}", numberOfLines.ToString());
        
        var result = chatCompletionsService.Complete(formattedMsg);

        logger.LogInformation($"Article with title '{title}' and general idea '{generalIdea}' was created");

        return result;
    }
}
```

E a *tool* que enviar emails:

```
using System.Text.Json;
using OpenAI.Assistants;

namespace AzureOpenAiFunctionCalling.AzureOpenAi.Tools;

public class EmailTool : ITool
{
    public static string Name => nameof(EmailTool);

    public static FunctionToolDefinition Definition => new(
        name: Name,
        description: "Send emails",
        parameters: BinaryData.FromObjectAsJson(
            new
            {
                Type = "object",
                Properties = new
                {
                    ReceiverEmail = new
                    {
                        Type = "string",
                        Description = "E-mail of the person who will receive the email",
                    },
                    Subject = new
                    {
                        Type = "string",
                        Description = "E-mail subject",
                    },
                    Message = new
                    {
                        Type = "string",
                        Description = "Email message",
                    },
                },
                Required = new[] { "ReceiverEmail", "Subject", "Message" },
            },
            new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));

    public string Execute(ILogger logger, string receiverEmail, string subject, string message)
    {
        logger.LogInformation($"Email sent to '{receiverEmail}' with subject '{subject}' and message '{message}'");

        return "Email sent successfully";
    }
}
```

Precisamos atualizar a criação do assistente para incluir as *tools.* Dessa forma, os assistentes criados irão saber que ele pode utilizá-las quando preciso.

```
  var assistantOptions = new AssistantCreationOptions
        {
            Name = request.Name,
            Instructions = request.Description,
            ToolResources = new(),
            Tools =
            {
                EmailTool.Definition,
                BlogArticleWriter.Definition
            },
        };
```

Vamos implementar o serviço que chamará as *tools* definidas. Ele será responsável por decidir qual *tool* será chamada via *function calling*.

```
public interface IToolHandler
{
    ToolOutput Handle(RequiredAction requiredAction);
}
```

```
using System.Text.Json;
using AzureOpenAiFunctionCalling.AzureOpenAi.Services.ChatCompletions;
using AzureOpenAiFunctionCalling.AzureOpenAi.Tools;
using OpenAI.Assistants;

namespace AzureOpenAiFunctionCalling.AzureOpenAi.Services.ToolHandler;

public class ToolHandler(ILogger<ToolHandler> logger, IChatCompletionsService chatCompletionsService) : IToolHandler
{
    public ToolOutput Handle(RequiredAction requiredAction)
    {
        using JsonDocument argumentsJson = JsonDocument.Parse(requiredAction.FunctionArguments);

        if (requiredAction.FunctionName == EmailTool.Name)
        {
            return HandlerEmailTool(requiredAction, argumentsJson);
        }
        
        if (requiredAction.FunctionName == BlogArticleWriter.Name)
        {
            return HandlerBlogArticleWriterTool(requiredAction, argumentsJson);
        }
        
        return null!;
    }

    private ToolOutput HandlerBlogArticleWriterTool(RequiredAction requiredAction, JsonDocument argumentsJson)
    {
        string title = argumentsJson.RootElement.GetProperty("title").GetString()!;
        string generalIdea = argumentsJson.RootElement.GetProperty("generalIdea").GetString()!;
        int numberOfLines = argumentsJson.RootElement.GetProperty("numberOfLines").GetInt32()!;

        var result = new BlogArticleWriter().Execute(
            logger, 
            chatCompletionsService, 
            title, 
            generalIdea, 
            numberOfLines);
        
        return new ToolOutput(requiredAction.ToolCallId, result);
    }

    private ToolOutput HandlerEmailTool(RequiredAction requiredAction, JsonDocument argumentsJson)
    {
        string receiverEmail = argumentsJson.RootElement.GetProperty("receiverEmail").GetString()!;
        string subject = argumentsJson.RootElement.GetProperty("subject").GetString()!;
        string message = argumentsJson.RootElement.GetProperty("message").GetString()!;

        var result = new EmailTool().Execute(
            logger, 
            receiverEmail, 
            subject,
            message);
        
        return new ToolOutput(requiredAction.ToolCallId, result);
    }
}
```

Precisamos atualizar o *loop* para fazer as chamadas das *tools* (quando necessário):

```
 do
        {
            await Task.Delay(TimeSpan.FromMilliseconds(100));
            threadRun = await assistantClient.GetRunAsync(thread.Id, threadRun.Id);
            
            if (threadRun.Status == RunStatus.RequiresAction)
            {
                List<ToolOutput> toolOutputs = new();
                foreach (var requiredAction in threadRun.RequiredActions)
                {
                    var result = toolHandler.Handle(requiredAction);
                    
                    toolOutputs.Add(result);
                }
                
                threadRun = await assistantClient.SubmitToolOutputsToRunAsync(threadRun, toolOutputs);
            }
        }
        while (threadRun.Status == RunStatus.Queued || threadRun.Status == RunStatus.InProgress);
```

Com isso pronto, podemos criar um novo prompt. Nesse exemplo, irei pedir para o assistente criar um artigo sobre C# e depois enviar para o meu e-mail. Dessa forma, o assistente deverá utilizar as duas *tools* criadas anteriormente.

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2024/07/Screen-Shot-2024-07-12-at-14.44.31.png)

```
"Create an article about C# (basic introduction to the language) in 100 words. After that, email talles.dsv@gmail.com with the article as message"
```

Temos o resultado:

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2024/07/Screen-Shot-2024-07-12-at-14.45.05.png)

Vendo os *logs,* podemos ver que as duas *tools* foram utilizadas sequencialmente!

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2024/07/Screen-Shot-2024-07-12-at-14.46.30.png)

E é isso pessoal! Teremos muito mais para falar sobre essa ferramenta incrível nos posts futuros.

Você já pode baixar o projeto e não esquece de me seguir no [LinkedIn](https://www.linkedin.com/in/tallesvaliatti/?ref=azurebrasil.cloud)!

Até a próxima, abraços!

[GitHub - TallesValiatti/AzureOpenAiFunctionCallingContribute to TallesValiatti/AzureOpenAiFunctionCalling development by creating an account on GitHub.![](https://github.githubassets.com/assets/pinned-octocat-093da3e6fa40.svg)GitHubTallesValiatti![](https://opengraph.githubassets.com/e68536d1ce154c75c3ba77fdc40702c9c148e5fd29a3df64d0964dca195cdc36/TallesValiatti/AzureOpenAiFunctionCalling)](https://github.com/TallesValiatti/AzureOpenAiFunctionCalling?ref=azurebrasil.cloud)