# Entendendo a Biblioteca Azure AI Persistent Agents

Durante meus estudos para a AI-102 e a natureza do meu trabalho, eu trabalhei com a Assistant API da OpenAi e com os Agentes do Azure AI Foundry. Percebi que muita coisa tava sendo abstraída para fazer os meus agentes de uma maneira rápida e eficiente. A ideia da biblioteca **Azure.AI.Agents.Persistent** é parecido com a funcionalidade do Assistant Api, onde um é focado apenas na OpenAi e o outro é focado em vários modelos disponíveis no Azure AI Foundry.

Neste artigo, vou apresentar a biblioteca que facilita a criação de agentes inteligentes, abstraindo boa parte da lógica que outras frameworks exigem implementações manuais e explicar um pouco o que ela está abstraindo e dando alguns conceitos que vamos explorar em outros artigos usando Semantic Kernel.

Qual?? A que está no título obviamente. Estou falando do **Persistent Agent**, uma biblioteca da Microsoft que integra com modelos LLMs do **Azure AI Foundry**. Um diferencial importante: alguns desses modelos suportam **memória de longo prazo de conversas**, por meio do conceito de **Threads** e é ai que o negócio fica interessante.

### Mas o que é uma Thread?

Uma **Thread** não é apenas um banco de dados onde o histórico da conversa é salvo. Embora tecnicamente ela armazene esse histórico, eu entendo melhor como um **serviço** que:

* Armazena **tudo** que passa pelo agente: mensagens do usuário, respostas do modelo e uso de ferramentas.
    
* Reconstrói o **contexto da conversa** com base nesse histórico, alimentando o modelo com as informações relevantes no momento da resposta.
    

Isso permite que a conversa evolua de forma mais natural, como se o agente "lembrasse" do que foi dito anteriormente.

### Como esse contexto é montado?

A estratégia exata usada pela thread para selecionar e injetar partes do histórico no prompt não é documentada, mas algumas abordagens são possíveis:

* **Sliding Window**: mantém uma janela deslizante das últimas interações e inclui essas mensagens diretamente no prompt.
    
* **VectorStore (RAG)**: transforma as mensagens em embeddings vetoriais e armazena em uma base vetorial. Quando o usuário envia uma nova mensagem, o sistema busca no vetorstore as mensagens mais próximas semanticamente e as usa como contexto.
    

![Diagrama de como é gerado o contexto](https://cdn.hashnode.com/res/hashnode/image/upload/v1752428784568/48dcf5f0-8517-458a-802b-c4464845872a.png align="center")

### Qual a vantagem da Thread?

Na prática, quando você usa `PersistentAgent` com suporte a threads, não precisa implementar esses mecanismos manualmente. A biblioteca se encarrega de:

* Criar e manter a thread automaticamente.
    
* Armazenar interações.
    
* Decidir como e o que trazer de volta para o prompt do modelo com base no histórico.
    
* Rapidez na hora de implementar um agente com memória de longo prazo.
    

### Qual a desvantagem?

Você não tem controle de muita coisa. no Azure Ai Foundry (basic) a Thread é armazenada em algum local no seu tenant onde você não tem acesso (bom, você consegue consultar o contéudo, mas não sabe onde está armazenado), no Azure Ai Foundry (standard) a Thread é armazenada num banco CosmosDb.

Você não consegue definir quais estratégias vão ser usadas.

### Outras técnicas para montar o contexto

#### 1\. **Entity Tracking (Memória de Entidades)**

* **Como funciona:** o sistema extrai e rastreia entidades importantes (nomes, datas, locais, intenções) ao longo da conversa e reusa essas informações nos prompts. (Pra quem já viu Document Intelligence e outras ferramentas de IA, a Entidade Nomeada).
    
* **Exemplo:** se o usuário menciona "minha esposa Ana" e depois disser "ela quer ir ao médico", o sistema sabe que "ela" = "Ana".
    

---

#### 2\. **Summarization Memory (Memória por Resumo)**

* **Como funciona:** a cada N interações, o sistema gera um **resumo condensado** da conversa até aquele ponto. Esse resumo é usado nos próximos prompts como contexto. (Um snapshopt da conversa até aquele momento.)
    
* **Vantagem:** reduz consumo de tokens mantendo o “contexto" da conversa.
    
* **Desvantagem:** pode perder detalhes finos importantes.
    

> 🔁 Pode ser feito de forma incremental (soma novos eventos ao resumo) ou periódica.

---

#### 3\. **Hierarchical Memory (Memória Hierárquica)**

* **Como funciona:** mantém diferentes camadas de memória:
    
    * **Curto prazo:** últimas interações (Sliding Window)
        
    * **Médio prazo:** contexto condensado (Summarization)
        
    * **Longo prazo:** base vetorial ou banco de conhecimento
        

---

#### 4\. **Topic-based Memory**

* **Como funciona:** armazena trechos da conversa agrupados por tópicos detectados automaticamente (ex: “viagem”, “projeto”, “problema técnico”) e resgata o que for mais relevante.
    
* **Técnica:** geralmente via clustering de embeddings ou classificação supervisionada.
    

> 🔍 Útil em sessões longas onde o usuário muda de assunto com frequência.

---

#### 5\. **FSM / Workflow-driven Context**

* **Como funciona:** a memória é controlada por um fluxo de estado (ex: máquina de estados finita), onde cada etapa tem dados associados ao contexto.
    
* **Vantagem:** previsível e mais interpretável.
    
* **Usado em:** bots de atendimento, agentes com objetivo claro.
    

---

#### 6\. **Tool-calling with Memory Storage**

* **Como funciona:** o agente chama uma ferramenta (plugin/API interna) que lê/escreve em um repositório de memória estruturada (ex: banco SQL, Redis, MongoDB).
    
* **Uso comum:** recuperação de fatos ou preferências salvas do usuário.
    

### **Show me the code**

#### Requisitos para rodar o código

* Azure Subscription
    
* Recurso: Azure AI Foundry
    
* Um modelo criado no Azure AI Foundry (usei o gpt-4.1-mini
    

---

A estrutura que eu sigo nessas samples é uma minimal api com um Register para os módulos e o serviço com os métodos que vamos utilizar. (vou fazer um template disso logo logo). Dessa maneira se você quiser expandir você pode criar novos registros e novos módulos.

O código completo está no meu [repositório do GitHub](https://github.com/carlosmachel/azure-ai-agents-basic)

Vou mostrar alguns pontos importantes, mas o código funcional está no meu github (Deixa uma estrelinha lá por favor? Me ajuda muito).

1. Vamos criar a nossa Solution
    

```bash
mkdir azure-ai-agents-basic && cd azure-ai-agents-basic && dotnet new sln 
```

2. Vamos criar o projeto em WebApi e associar com a nossa Solution
    

```bash
dotnet new webapi -n AzureAiAgent.Api && dotnet sln azure-ai-agents-basic.sln add AzureAiAgent.Api/AzureAiAgent.Api.csproj
```

(Se não quiser fazer assim, crie pela sua IDE preferida)

3. Vamos precisar da URI e do Modelo LLM que vamos usar. (Aqui lembrando do requisito para rodar, precisa desse recurso do Azure AI Foundry)
    

```json
"AzureAiSettings" : {
  "Uri": "",
  "Model": ""
},
```

E no **Program.cs** adicionar para utilizar mais tarde no serviço.

```csharp
builder.Services.Configure<AzureAiSettings>(builder.Configuration.GetSection("AzureAiSettings"));
```

4. Caminhe até a pasta onde está **AzureAiAgent.Api** e rode adicione esses pacotes (da para utilizar o Azure.AI.Agents.Persistent sozinho, mas vou mostrar a criação de Thread usando o Azure.AI.Projects.
    

```bash
dotnet add package Azure.AI.Agents.Persistent --version 1.1.0-beta.1
dotnet add package Azure.AI.Projects --version 1.0.0-beta.9
```

3. A implementação das rotas que vamos utilizar
    

```csharp
        app.MapPost("/ai-agent", async (
                [FromServices] Service service,
                [FromQuery] string name, [FromQuery] string instructions) =>
            {
                var agentId = await service.CreateAgentAsync(name, instructions);
                return Results.Ok(agentId);
            })
            .WithTags("Ai Agents");
        
        app.MapGet("/ai-agent/create-thread", async (
                [FromServices] Service service) =>
            {
                var agentId = await service.CreateThreadAsync();
                return Results.Ok(agentId);
            })
            .WithTags("Ai Agents");
        
        app.MapGet("/ai-agent/run", async (
                [FromServices] Service service,
                [FromQuery] string agentId,
                [FromQuery] string threadId,
                [FromQuery] string userInput) =>
            {
                var result = await service.RunAsync(agentId, threadId, userInput);
                return Results.Ok(result);
            })
            .WithTags("Ai Agents");
```

E por fim o nosso exemplo de serviço de implementação:

4. Primeiro, criaremos o agente usando a biblioteca **Azure.AI.Agents.Persistent** onde tem que passar a URI e estou usando a DefaultAzureCredential (então tem que usar o az login para logar na sua subscription do Azure).
    

```csharp
public async Task<string> CreateAgentAsync(string agentName, string instructions)
    {
        var agentClient = new PersistentAgentsClient(options.Value.Uri, new DefaultAzureCredential());
        PersistentAgent agent = await agentClient.Administration.CreateAgentAsync(options.Value.Model,
            name: agentName,
            instructions: instructions);
        return agent.Id;
    }
```

Repare que nós temos que criar o PersistentAgentsClient e a partir dai criar o agente passando o nome do Agente que vc quer dar e a instruções (exemplo: agentName = Agente Básico, instructions = “Você é um agente útil”).

Com isso a gente tem o nosso Agente, mas ainda não tem a nossa Thread!

5. Método para criar a Thread.
    

```csharp
public async Task<string> CreateThreadAsync()
{
    var projectClient = new AIProjectClient(new Uri(options.Value.Uri), new DefaultAzureCredential());
    var agentClient = projectClient.GetPersistentAgentsClient();

    PersistentAgentThread thread = await agentClient.Threads.CreateThreadAsync();
    return thread.Id;
}
```

Estou mostrando um exemplo usando o AIProjectClient que com ele vc pega o cliente do PersistentAgentClient. Percebe que a gente chega no mesmo client no final? A biblioteca do **Azure.AI.Projects** dd é uma abstração maior ao Projeto no Azure Ai Foundry. Você consegue com ele criar outros clients.

6. Por fim, criar um serviço para fazer um Run que é quando o usuário interage com o Agente.
    

```csharp
public async Task<IEnumerable<string>> RunAsync(string assistantId, string threadId, string userInput)
{
    var projectClient = new AIProjectClient(new Uri(options.Value.Uri), new DefaultAzureCredential());
    var agentClient = projectClient.GetPersistentAgentsClient();
    
    PersistentAgent agent = await agentClient.Administration.GetAgentAsync(assistantId);
    PersistentAgentThread thread = await agentClient.Threads.GetThreadAsync(threadId);
    
    await agentClient.Messages.CreateMessageAsync( threadId, role: MessageRole.User, content: userInput);
    
    ThreadRun run = await agentClient.Runs.CreateRunAsync(thread, agent);

    do
    {
        await Task.Delay(TimeSpan.FromMilliseconds(500));
        run = await agentClient.Runs.GetRunAsync(thread.Id, run.Id);
    }
    while (run.Status == RunStatus.Queued
           || run.Status == RunStatus.InProgress
           || run.Status == RunStatus.RequiresAction);
    
    var messagesResponse = agentClient.Messages.GetMessagesAsync(thread.Id, order: ListSortOrder.Descending);
    var result = new List<string>();

    await foreach (var threadMessage in messagesResponse)
    {
        if (threadMessage.Role == MessageRole.Agent &&
            threadMessage.ContentItems.FirstOrDefault() is MessageTextContent messageTextContent)
        {
            result.Add(messageTextContent.Text);
        }
    }

    return result;
}
```

A princípio parece que é muita coisa ocorrendo nesse código.

O objetivo desse método é usar as informações obtidas nos últimos dois métodos e rodar a pergunta de um usuário

Obter o Agente pelo assistantId, pegar a thread pelo threadId.

Observem que aqui começa a mágica: a criação da mensagem não tem nada haver com o Agente e sim com a Thread. Adiciona a mensagem do Usuário na Thread.

```csharp
await agentClient.Messages.CreateMessageAsync( threadId, role: MessageRole.User, content: userInput);
```

Desse ponto do método pra desse ponto pra baixo é a conexão do Agente com a Thread a execução do Agente e recuperando o retorno do agente.

Esse ponto tem uma sequencia de coisas acontecendo.

* A Thread seleciona o que vai pro contexto do Agente. (O ponto central desse post)
    
* e o agente gera a mensagem e a resposta é salva na Thread
    
* A busca da resposta do agente é feita pela Thread novamente. Você pode ver que não tem mais relação nenhuma agora com o agente. O papel ele ja fez.
    

Nos próximos artigos eu vou trazer um pouco mais da flexibilidade do [Semantic Kernel](https://learn.microsoft.com/en-us/semantic-kernel/overview/) para implementar essas estratégias e dar o total controle sobre o agente. E sinceramente aprender como implementar junto com vocês.

### Referências

[Azure.AI.Agents.Persistent](https://learn.microsoft.com/en-us/dotnet/api/overview/azure/ai.agents.persistent-readme?view=azure-dotnet)

[Azure.AI.Projects](https://learn.microsoft.com/en-us/python/api/overview/azure/ai-projects-readme?view=azure-python-preview)

[Repositório do Github](https://github.com/carlosmachel/azure-ai-agents-basic)
