Agent bindings no Azure Functions: como usar com Python

Os agent bindings no Azure Functions permitem adicionar raciocínio de IA a funções Python sem abandonar o modelo de gatilhos e bindings que você já conhece. Em vez de reescrever a aplicação em volta de um agente, você recebe um Agent pronto no handler e decide, em código, quando chamá-lo.
Neste artigo, você vai entender como o recurso funciona por dentro. Além disso, vai montar uma função HTTP com um agente do Microsoft Agent Framework e levar esse mesmo agente para uma orquestração do Durable Functions.
O que são agent bindings no Azure Functions
Um agent binding é um binding de entrada controlado pela extensão, que entrega ao handler um objeto Agent já construído. Em outras palavras, a extensão lê as instruções em Markdown, cria o cliente de chat, monta o agente e injeta tudo como parâmetro tipado da função.
Os agent bindings no Azure Functions estão em preview e, por enquanto, atendem apenas aplicações Python no modelo de programação v2. Além disso, o Microsoft Agent Framework é o único provider disponível nesta fase, e o pacote exige Python 3.13 ou superior.
A arquitetura, no entanto, nasceu neutra em relação ao provider. Ou seja, a proposta permite que outros SDKs de agentes entrem no futuro por meio de pacotes específicos.
Na prática, o agente vira mais uma dependência da função, assim como um container do Blob Storage ou uma fila do Service Bus. Você declara o binding com um decorator, e a extensão cuida de entregar o objeto pronto.
A documentação oficial reúne todos os detalhes da preview em Agent bindings for Python in Azure Functions. Vale manter essa página por perto, porque nomes de pacotes e configurações ainda podem mudar.
Quando usar agent bindings no Azure Functions
A maioria dos fluxos de IA em produção mistura dois tipos de trabalho bem diferentes. Por isso, separar essas duas naturezas é o primeiro passo para usar o recurso direito.
Primeiro, existem operações com contrato preciso: ler a requisição, validar o schema, checar autorização, calcular valores e montar a resposta. Por outro lado, existem tarefas que pedem interpretação, como classificar um conteúdo, resumir evidências ou sugerir uma ação a partir do contexto.
O código determinístico deve continuar dono das primeiras tarefas, enquanto o agente cuida apenas das segundas. Os agent bindings no Azure Functions nascem exatamente para essa divisão.
Pense em uma API de pedidos. A função valida o pedido, reduz os dados aos campos relevantes e só então pede ao agente uma avaliação do risco de entrega.
Com isso, a função continua responsável pelo contrato HTTP e decide o que fazer com a resposta do modelo. Essa separação traz benefícios práticos:
- Gatilhos de HTTP, fila, timer, Event Grid e Service Bus continuam funcionando como em qualquer function app Python v2.
- O código define a fronteira de confiança e limita o que chega ao modelo.
- As instruções do agente ficam separadas da configuração de runtime e do modelo.
- O handler pode chamar o agente de forma condicional, inspecionar a resposta ou simplesmente pular essa etapa.
- A mesma definição de agente funciona dentro de uma orquestração do Durable Functions.
Além disso, você depura a aplicação localmente como qualquer outra function app Python. Assim, os breakpoints funcionam tanto na lógica determinística quanto no trecho que chama o agente.
Estrutura de um projeto com agent bindings no Azure Functions
Um projeto com agent bindings no Azure Functions continua sendo uma function app Python v2 comum. A diferença está no pacote do provider e em um ou mais arquivos de instruções.
agent-binding-app/
├── function_app.py
├── host.json
├── requirements.txt
├── local.settings.json
├── order-fulfillment.agent.md
├── skills/ # skills opcionais
│ └── fulfillment-policy/
│ └── SKILL.md
└── mcp.json # servidores MCP remotos opcionais
Cada arquivo tem uma responsabilidade clara dentro do projeto:
function_app.py: define os gatilhos, a lógica determinística, a factory do cliente e a invocação do agente.order-fulfillment.agent.md: guarda as instruções de um agente em texto UTF-8 puro.requirements.txt: seleciona o provider e o pacote do cliente de chat.local.settings.json: traz as configurações locais de storage e do projeto no Foundry, e nunca deve ir para o repositório.skills/: contém skills baseadas em arquivo, que o provider descobre sozinho.mcp.json: configura servidores MCP remotos e listas de ferramentas permitidas.
Por fim, cada nome de agente resolve para exatamente um arquivo. Assim, agent_name="order-fulfillment" aponta para order-fulfillment.agent.md na raiz ou para agents/order-fulfillment.agent.md.
Se os dois arquivos existirem, a definição fica ambígua e a aplicação falha na inicialização. Da mesma forma, nomes com caminhos absolutos, separadores de diretório ou componentes de travessia não passam pela validação.
Como criar uma função com agent bindings no Azure Functions
O cenário mais simples é uma função HTTP em que o raciocínio termina dentro da própria invocação. Nesse caso, o resultado do agente vai direto na resposta para o cliente.
Antes de começar, você precisa de uma assinatura do Azure e de um projeto no Microsoft Foundry com um modelo implantado. Também entram na lista o Azure Functions Core Tools, o Azurite ou uma conta de storage e o Azure CLI.
Sua identidade local, por sua vez, precisa ter acesso ao projeto do Foundry. Em seguida, crie o projeto e o ambiente virtual:
func init agent-binding-quickstart --worker-runtime python --model V2
cd agent-binding-quickstart
python3.13 -m venv .venv
source .venv/bin/activate
Instalando os pacotes
Substitua o conteúdo do requirements.txt pelas dependências abaixo e rode python -m pip install -r requirements.txt em seguida:
azure-functions
azurefunctions-agents-extensions-agent-framework[mcp]
agent-framework-foundry
azure-identity
O pacote azurefunctions-agents-extensions-agent-framework integra o binding ao Microsoft Agent Framework. Já o extra mcp instala as dependências opcionais para descobrir servidores remotos definidos no mcp.json.
O agent-framework-foundry fornece o FoundryChatClient, enquanto o azure-identity entrega a credencial usada no desenvolvimento local. Depois disso, preencha o local.settings.json:
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "python",
"FOUNDRY_PROJECT_ENDPOINT": "https://<resource-name>.services.ai.azure.com/api/projects/<project-name>",
"FOUNDRY_MODEL": "<model-deployment-name>"
}
}
A chave AzureWebJobsStorage aponta o host local para o Azurite. As outras duas identificam o projeto e o deployment do modelo no Foundry.
Escrevendo as instruções do agente
Crie o arquivo order-fulfillment.agent.md na raiz da aplicação com as instruções do agente:
You are an order fulfillment specialist.
The supplied order has already been validated and minimized by application code.
Treat the supplied fields as trusted facts. Explain operational risk, identify
missing fulfillment context, and return a concise, actionable response.
A extensão envia o arquivo inteiro ao provider como instruções do agente. Portanto, não adianta colocar front matter YAML, nome de modelo ou declaração de ferramentas nesse arquivo.
Nos agent bindings no Azure Functions, o modelo pertence à factory do cliente, e as ferramentas Python continuam sendo configuração explícita da aplicação. Essa separação ajuda bastante na revisão de código, porque quem ajusta o prompt não mexe no runtime.
Configurando o chat client
O AgentFunctionApp recebe uma factory sem argumentos que devolve o cliente de chat. Neste exemplo, a factory cria um cliente do Foundry a partir das variáveis de ambiente e autentica com o DefaultAzureCredential:
import os
def create_chat_client():
from agent_framework.foundry import FoundryChatClient
from azure.identity.aio import DefaultAzureCredential
# Cria um cliente novo a cada invocação da função
return FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ["FOUNDRY_MODEL"],
credential=DefaultAzureCredential(),
)
Nos agent bindings no Azure Functions, a extensão chama essa factory a cada invocação da função. Dessa forma, cada execução recebe seu próprio cliente, agente e credencial, e a extensão fecha esses recursos ao final.
Preparando os dados em código
Antes de chamar o agente, selecione apenas os campos necessários para a avaliação:
def prepare_order(payload: dict, order_id: str) -> dict:
# Envia ao modelo somente os campos relevantes para a entrega
return {
"order_id": order_id,
"customer_id": payload["customer"]["id"],
"currency": str(payload.get("currency", "USD")).upper(),
"shipping_country_or_region": payload["shipping"]["country_or_region"],
"shipping_method": payload["shipping"]["method"],
"items": payload["items"],
}
O helper usa Python comum, e essa escolha tem um propósito claro nos agent bindings no Azure Functions. Ele cria uma fronteira explícita entre os dados que chegam na requisição e o que o modelo enxerga.
Em produção, esse trecho concentra a validação de schema, as regras de autorização, os campos calculados e a minimização dos dados. Ou seja, nada sensível chega ao modelo por acidente.
Vinculando e invocando o agente
Agora crie o AgentFunctionApp, mantenha o gatilho HTTP padrão e adicione o decorator markdown_agent:
import json
import azure.functions as func
from agent_framework import Agent
from azurefunctions.agents.extensions.agent_framework import AgentFunctionApp
app = AgentFunctionApp(client_factory=create_chat_client)
@app.route(route="orders/{orderId}", methods=["POST"])
@app.markdown_agent(
arg_name="order_agent",
agent_name="order-fulfillment",
)
async def process_order(
req: func.HttpRequest,
order_agent: Agent,
) -> func.HttpResponse:
try:
prepared_order = prepare_order(
req.get_json(),
req.route_params["orderId"],
)
except (KeyError, TypeError, ValueError):
return func.HttpResponse(
body=json.dumps({"error": "Order failed validation."}),
status_code=400,
mimetype="application/json",
)
# O agente só roda quando o handler chama run() de forma explícita
response = await order_agent.run(
json.dumps(
{
"order": prepared_order,
"task": "assess fulfillment readiness",
}
)
)
return func.HttpResponse(
body=json.dumps(
{
"order_id": prepared_order["order_id"],
"assessment": response.text,
}
),
mimetype="application/json",
)
Dois nomes ligam o decorator à função:
arg_name="order_agent": precisa coincidir com o nome do parâmetro injetado no handler.agent_name="order-fulfillment": identifica o arquivo de instruções, sem o sufixo.agent.md.
O agente não roda sozinho quando a função começa. Na verdade, o handler chama await order_agent.run(...) de forma explícita, então a fronteira do raciocínio fica visível e testável no código.
Se você precisar de mais de um agente na mesma função, basta empilhar decorators markdown_agent. Nesse caso, cada um recebe seu próprio arg_name e seu próprio parâmetro no handler.
O que acontece em cada invocação
Quando o host compila o binding, ele valida a assinatura do handler e a definição do agente. Em seguida, a cada invocação, a extensão executa esta sequência:
- Localiza e carrega o arquivo de instruções solicitado.
- Chama a factory configurada para criar o cliente de chat.
- Combina as instruções com as ferramentas Python configuradas e com as skills e servidores MCP descobertos.
- Constrói o
Agentdo Microsoft Agent Framework e o injeta no parâmetro do handler. - Fecha os recursos da invocação depois de sucesso, falha ou cancelamento.
A extensão pode manter em cache as definições compiladas e a descoberta do provider. No entanto, clientes e credenciais vivos nunca passam de uma invocação para outra.
A validação dos agent bindings no Azure Functions também pega erros comuns cedo. Por exemplo, ela acusa arquivo .agent.md ausente, parâmetro com nome divergente, mcp.json malformado ou payload que não serializa em JSON.
Sempre que possível, a extensão ainda preserva o nome da função, o ID da invocação e o ID da instância durable na fronteira com o provider. Com isso, a correlação nos logs fica muito mais simples.
Ferramentas, skills e MCP nos agent bindings do Azure Functions
Além das instruções, um agente geralmente precisa de ferramentas. Nos agent bindings do Azure Functions, você passa ferramentas Python de forma explícita, no nível da aplicação ou de um binding específico.
Por exemplo, a função abaixo disponibiliza lookup_inventory apenas para o order_agent:
def lookup_inventory(product_id: str) -> str:
"""Return the available inventory for a product."""
return f"Inventory is available for {product_id}."
@app.markdown_agent(
arg_name="order_agent",
agent_name="order-fulfillment",
tools=[lookup_inventory],
)
async def check_order(
req: func.HttpRequest,
order_agent: Agent,
) -> func.HttpResponse:
# Apenas este binding enxerga a ferramenta lookup_inventory
response = await order_agent.run(req.get_body().decode())
return func.HttpResponse(response.text)
Skills e servidores MCP seguem outro caminho, porque a extensão os descobre a partir da raiz da aplicação. As skills ficam em skills/<skill-name>/SKILL.md, enquanto os servidores remotos ficam declarados no mcp.json.
Se o tema MCP ainda soa novo para você, vale ler o artigo sobre o Microsoft Binlog MCP Server. Ele mostra um servidor MCP aplicado ao diagnóstico de builds .NET na prática.
Há, contudo, três cuidados importantes nesta preview:
- Todo agent binding da function app recebe todas as skills e todos os servidores MCP descobertos.
- Ainda não existe forma de selecionar só um subconjunto dessas capacidades por binding.
- Servidores MCP locais ou via stdio não funcionam, apenas HTTP e streamable HTTP.
Como skills e ferramentas MCP podem executar operações privilegiadas, coloque na aplicação apenas o que todos os agentes podem usar. Quando os limites de acesso forem diferentes, separe os agentes em function apps distintas no Azure Functions.
Por fim, o mcp.json aceita referências a variáveis de ambiente para URLs, headers e escopos de autenticação. Assim, você não precisa versionar segredos junto com o código.
Agent bindings no Azure Functions com Durable Functions
Uma chamada direta resolve bem uma operação curta de requisição e resposta. Contudo, o processo de pedidos pode crescer e exigir checagens de estoque, políticas, espera por sistemas externos e uma resposta final muito depois da requisição original.
Para esse cenário, os agent bindings no Azure Functions se integram ao Durable Functions, que persiste o estado da orquestração. Para habilitar a integração, instale o provider com o extra durable:
azure-functions
azurefunctions-agents-extensions-agent-framework[durable,mcp]
agent-framework-foundry
azure-identity
O extra durable instala o suporte usado pelo AgentFunctionApp nas orquestrações. Enquanto isso, o extra mcp mantém a descoberta de servidores MCP do cenário direto.
Iniciando a orquestração
O starter HTTP usa o binding de cliente durable padrão. Ele valida o JSON, inicia o order_orchestrator e devolve o payload de gerenciamento:
import azure.durable_functions as df
@app.route(route="orders/orchestrations", methods=["POST"])
@app.durable_client_input(client_name="client")
async def start_order_orchestration(
req: func.HttpRequest,
client: df.DurableFunctionsClient,
) -> func.HttpResponse:
try:
order = req.get_json()
except ValueError:
return func.HttpResponse(
body=json.dumps({"error": "Order failed validation."}),
status_code=400,
mimetype="application/json",
)
instance_id = await client.start_new(
"order_orchestrator",
client_input=order,
)
# Devolve as URLs para acompanhar a instância
management = client.create_http_management_payload(req, instance_id)
return func.HttpResponse(
body=json.dumps(management),
status_code=202,
mimetype="application/json",
headers={
"Location": management["statusQueryGetUri"],
"Retry-After": "10",
},
)
A resposta 202 Accepted desacopla o tempo de vida do workflow da requisição HTTP. Então, quem chamou consulta o statusQueryGetUri para acompanhar a instância e buscar a saída final.
Preparando a entrada em uma activity
A preparação do pedido agora vira uma activity comum:
@app.activity_trigger(input_name="order")
def prepare_order_activity(order: dict) -> dict:
# Validação e minimização ficam fora do orquestrador
return {
"order_id": order["order_id"],
"customer_id": order["customer"]["id"],
"currency": str(order.get("currency", "USD")).upper(),
"shipping_country_or_region": order["shipping"]["country_or_region"],
"shipping_method": order["shipping"]["method"],
"items": order["items"],
}
Activities representam o lugar certo para validação, cálculos e minimização de dados. Afinal, esse tipo de trabalho não deve rodar dentro do orquestrador.
Chamando o agente no orquestrador
O orquestrador coordena a activity e a chamada ao agente:
@app.orchestration_trigger(context_name="context")
def order_orchestrator(context: DurableAgentContext):
prepared_order = yield context.call_activity(
"prepare_order_activity",
context.get_input(),
)
# Agenda a chamada ao agente de forma segura para o replay
assessment = yield context.call_agent(
"order-fulfillment",
{
"order": prepared_order,
"task": "assess fulfillment risk",
},
)
return {
"order_id": prepared_order["order_id"],
"risk_assessment": assessment,
}
O método context.call_agent() recebe o nome lógico do agente e uma entrada compatível com JSON. Depois disso, a orquestração pode usar o resultado em outras activities, passar para outra etapa de raciocínio ou devolver como saída.
Por que o replay continua determinístico
Orquestradores do Durable Functions fazem replay para reconstruir o estado. Por isso, chamadas de rede e de modelo não podem rodar diretamente no código do orquestrador.
O call_agent() resolve esse problema ao agendar uma activity oculta, gerenciada pela extensão. Essa activity carrega as instruções, cria o cliente e o agente, chama o modelo e devolve um resultado serializável.
O histórico da orquestração registra esse resultado. Assim, durante o replay, o Durable Functions reaproveita a resposta em vez de chamar o modelo de novo.
Na prática, você ganha duas vantagens com os agent bindings no Azure Functions em orquestrações. Primeiro, não paga duas vezes pela mesma inferência, e além disso o orquestrador continua determinístico.
Executando agent bindings no Azure Functions localmente
Antes de subir a aplicação, rode az login para que o DefaultAzureCredential use sua identidade do Azure CLI ao acessar o Foundry. Em seguida, inicie o Azurite para o storage local:
azurite --silent --location .azurite
Depois, em outro terminal, suba o host do Functions:
func start
Para testar a função direta, envie um pedido para a rota com o ID na URL:
curl -X POST http://localhost:7071/orders/42 \
-H "Content-Type: application/json" \
-d '{"customer":{"id":"C-1007"},"currency":"usd","shipping":{"country_or_region":"ca","method":"overnight"},"items":[{"sku":"A-100","quantity":2,"unit_price":"24.95"}]}'
A resposta traz o ID da rota e a avaliação gerada pelo modelo:
{
"order_id": "42",
"assessment": "<model-generated fulfillment assessment>"
}
No caminho durable, o ID do pedido precisa ir no corpo, porque o starter não recebe esse valor como parâmetro de rota:
curl -X POST http://localhost:7071/orders/orchestrations \
-H "Content-Type: application/json" \
-d '{"order_id":"42","customer":{"id":"C-1007"},"currency":"usd","shipping":{"country_or_region":"ca","method":"overnight"},"items":[{"sku":"A-100","quantity":2,"unit_price":"24.95"}]}'
O starter devolve o ID da instância e as URLs de gerenciamento. Então, consulte o statusQueryGetUri até o runtimeStatus ficar Completed e leia o risk_assessment na saída.
Limitações dos agent bindings no Azure Functions em preview
Como todo recurso em preview, os agent bindings no Azure Functions ainda podem mudar nomes de pacotes, APIs e configuração antes da versão estável. Portanto, trate o recurso com cautela em ambientes críticos e fixe as versões dos pacotes no requirements.txt.
Algumas boas práticas ajudam a extrair valor desde já:
- Mantenha validação, autorização e minimização de dados sempre no código, antes de qualquer chamada ao agente.
- Escreva instruções curtas e objetivas no
.agent.md, com uma única tarefa delimitada por agente. - Trate a resposta do modelo como sugestão e decida no código como ela afeta o fluxo.
- Separe function apps quando agentes precisarem de conjuntos diferentes de ferramentas.
- Não versione o
local.settings.jsonnem coloque segredos nomcp.json.
Vale lembrar também que o recurso cobre apenas Python nesta fase. Se o seu time trabalha com .NET, o guia de Blazor AI components mostra outra forma de integrar agentes de IA ao ecossistema Microsoft.
Conclusão
Os agent bindings no Azure Functions trazem uma proposta madura para quem quer IA em produção. Em resumo, o código manda, e o agente executa uma tarefa de raciocínio bem delimitada.
Comece pela invocação direta quando o resultado pertence à execução atual da função. Depois, migre para o Durable Functions quando o processo precisar persistir progresso, coordenar várias etapas ou continuar após o fim da requisição.
Dessa forma, você aproveita gatilhos, bindings e orquestrações que já conhece. E, principalmente, adiciona raciocínio apenas onde ele realmente agrega valor.
FAQ
Os agent bindings no Azure Functions funcionam com C# ou JavaScript?
Ainda não. Nesta preview, o recurso atende apenas aplicações Python no modelo de programação v2, com Python 3.13 ou superior.
Posso usar outro SDK de agentes além do Microsoft Agent Framework?
Por enquanto, não. A arquitetura aceita providers diferentes, mas o Microsoft Agent Framework é o único disponível na preview.
Onde configuro o modelo usado pelo agente?
Na factory do cliente de chat, passada ao AgentFunctionApp. O arquivo .agent.md contém apenas instruções e a extensão não lê configuração de modelo dele.
O agente executa automaticamente quando a função dispara?
Não. O handler precisa chamar run() de forma explícita, então você decide se e quando o agente participa da execução.
Como evitar chamadas repetidas ao modelo em uma orquestração?
Use context.call_agent() no orquestrador. Assim, o histórico registra o resultado e o replay reaproveita a resposta em vez de chamar o modelo de novo.