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.
Referências
Subscribe to my newsletter
Read articles from Carlos Machel directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
