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.

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
Vou mostrar alguns pontos importantes, mas o código funcional está no meu github (Deixa uma estrelinha lá por favor? Me ajuda muito).
- Vamos criar a nossa Solution
mkdir azure-ai-agents-basic && cd azure-ai-agents-basic && dotnet new sln
- Vamos criar o projeto em WebApi e associar com a nossa Solution
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)
- Vamos precisar da URI e do Modelo LLM que vamos usar. (Aqui lembrando do requisito para rodar, precisa desse recurso do Azure AI Foundry)
"AzureAiSettings" : {
"Uri": "",
"Model": ""
},
E no Program.cs adicionar para utilizar mais tarde no serviço.
builder.Services.Configure<AzureAiSettings>(builder.Configuration.GetSection("AzureAiSettings"));
- 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.
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
- A implementação das rotas que vamos utilizar
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:
- 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).
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!
- Método para criar a Thread.
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.
- Por fim, criar um serviço para fazer um Run que é quando o usuário interage com o Agente.
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.
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 para implementar essas estratégias e dar o total controle sobre o agente. E sinceramente aprender como implementar junto com vocês.

