# Criar agentes de IA

Uma equipe de produto ou de operações cria agentes que concluem tarefas em vez de apenas responder: eles chamam APIs internas e ferramentas SaaS, mantêm o estado entre etapas e passam a vez a uma pessoa para aprovação quando necessário. Um modelo que pode chamar ferramentas ainda não decide nada sozinho. Algo precisa executar o loop, executar cada ferramenta que o modelo pede, parar antes de uma ação que uma pessoa precisa aprovar e lembrar onde a tarefa parou. Esta página executa esse loop como uma function na Azion com um modelo com tool calling de um provedor de terceiros, mantém as tarefas pausadas no KV Store e registra cada tarefa no SQL Database. O resultado é medido pela taxa de conclusão de tarefas, pelas etapas e pela latência por tarefa e pela parcela de ações que precisaram de aprovação humana.

Este caso de uso não cobre assistentes que apenas respondem a partir de conteúdo da empresa nem a exposição de ferramentas a agentes externos. Para esses, consulte [Criar e executar assistentes de IA para suporte ao cliente](/pt-br/documentacao/casos-de-uso/construir-e-executar-workloads-de-ai/criar-e-executar-assistentes-de-ia-para-suporte-ao-cliente/) e [Implantar servidores MCP remotos](/pt-br/documentacao/casos-de-uso/construir-e-executar-workloads-de-ai/implantar-servidores-mcp-remotos/).

## Pré-requisitos

- Uma aplicação e um workload que servem o seu domínio, com o **Application Accelerator** ativado, que o behavior **Run Function** exige. Para criá-los, consulte [Primeiros passos com Applications](/pt-br/documentacao/plataforma/applications/primeiros-passos/).
- KV Store e SQL Database habilitados na conta. Os dois estão em Preview e não vêm habilitados por padrão, então solicite acesso pelo [Technical Support](/pt-br/documentacao/suporte/).
- Um personal token com a permissão **Edit SQL Database**, para as chamadas ao banco de dados e ao KV Store. Para criar um, consulte [Gerencie personal tokens](/pt-br/documentacao/guias/plataforma/conta-e-billing/personal-tokens/).
- A [Azion CLI](/pt-br/documentacao/devtools/cli/), instalada e autorizada, para armazenar as variáveis de ambiente.
- Um provedor de terceiros cujo endpoint de chat aceite o formato de chat completions da OpenAI com definições de ferramentas, com a URL dele, o nome de um modelo que suporte tool calling e uma API key.
- Os valores da sua própria configuração. Esta página dá ao agente duas ferramentas sobre um serviço de pedidos em `https://api.example.com/v1`: `get_order`, que lê `GET /orders/{id}`, e `refund_order`, que chama `POST /orders/{id}/refunds` e precisa da aprovação de uma pessoa. Ela usa `agent-state` para o namespace do KV Store, `agent-history` para o banco de dados e `www.example.com` para o domínio. Substitua cada valor pelo seu em todas as etapas.

---

## Produtos necessários

| O agente precisa de                                                                                  | O que significa                                                                                    | Produto                 | Documentado em                                                                                                                                                                 |
| ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Um loop que envia a tarefa ao modelo, executa as ferramentas que ele pede e para quando ele responde | Uma function que chama o provedor e as APIs internas com `fetch()`                                 | Functions               | [Primeiros passos com Functions](/pt-br/documentacao/plataforma/functions/primeiros-passos/)                                                                                   |
| Uma tarefa pausada que retoma de onde parou                                                          | As mensagens da tarefa e a ação pendente, armazenadas sob o ID da tarefa até que uma pessoa decida | KV Store                | [KV Store API](/pt-br/documentacao/devtools/runtime/api-reference/kv-store/)                                                                                                   |
| Um registro de cada tarefa, das suas etapas e das suas aprovações                                    | Uma tabela `task_events` gravada pela API da Azion                                                 | SQL Database            | [Grave linhas no SQL Database a partir de uma function](/pt-br/documentacao/guias/desenvolvimento-de-aplicacoes/dados/gravar-linhas-no-sql-database-a-partir-de-uma-function/) |
| Regras que executam a function nos caminhos do agente                                                | O behavior **Run Function**, que exige o Application Accelerator na aplicação                      | Application Accelerator | [Execute uma função em um caminho e reverta-o](/pt-br/documentacao/guias/desenvolvimento-de-aplicacoes/functions-e-runtime/executar-uma-funcao-em-um-caminho-e-reverter/)      |

O modelo vem do provedor de terceiros. Uma equipe que executa o modelo no AI Inference chama um modelo cuja capacidade Tool calling é Yes em [Modelos de AI](/pt-br/documentacao/plataforma/ai-inference/modelos/), com o mesmo array `tools`.

---

## Arquitetura de referência

Esta página constrói o *Tool-calling agent over third-party LLMs*: cada etapa do loop chama a API do provedor, e as ferramentas são executadas na function.

```mermaid
%%{init: {"layout": "dagre", "themeVariables": {"fontSize": "13px"}, "flowchart": {"nodeSpacing": 12, "rankSpacing": 12, "padding": 6, "wrappingWidth": 70, "minNodeWidth": 40, "useMaxWidth": true}}}%%
flowchart TD
  Caller["Sua aplicação"] -->|"POST /api/agent/tasks"| Fn["function do agente"]
  Fn -->|"mensagens e definições de ferramentas"| LLM["provedor de terceiros"]
  LLM -->|"resposta ou tool calls"| Fn
  Fn -->|"get_order"| API["API do serviço de pedidos"]
  Fn -->|"refund_order: pausa"| KV["KV Store: agent-state"]
  Approver["Pessoa que aprova"] -->|"POST /api/agent/approve"| Fn
  KV -->|"retomada"| Fn
  Fn -->|"iniciada, pausada, concluída"| DB["SQL Database: task_events"]
  Fn -->|"resposta ou aguardando aprovação"| Caller
```

Leia o diagrama a partir da function. O loop fica na function, e cada volta dele passa pelo provedor: a seta para o provedor é percorrida uma vez por etapa, então a latência do provedor se multiplica pelo número de etapas, e as falhas dele podem encerrar uma tarefa em qualquer etapa. A seta para o serviço de pedidos só sai da Azion quando a ferramenta é um serviço externo. O KV Store e o SQL Database guardam o que precisa sobreviver a uma requisição: o estado de uma tarefa que espera por uma pessoa e o registro do que cada tarefa fez.

### Fluxo de dados

1. A sua aplicação envia uma tarefa para `POST /api/agent/tasks`, e a regra da aplicação executa a function do agente. A function dá um ID à tarefa, a registra como iniciada e envia a tarefa e as definições de ferramentas ao provedor com a chave do provedor.
2. Quando o modelo pede `get_order`, a function chama o serviço de pedidos com o token do backend e envia o resultado de volta ao modelo como a próxima mensagem.
3. Quando o modelo pede `refund_order`, a function armazena as mensagens da tarefa e o reembolso pendente no KV Store, registra a pausa e responde `awaiting_approval`.
4. Uma pessoa envia a decisão para `POST /api/agent/approve`. A function lê a tarefa do KV Store, executa o reembolso apenas quando aprovado e retoma o loop.
5. Quando o modelo responde com texto e sem tool call, a function registra a tarefa como concluída e retorna a resposta.
6. Uma chamada ao provedor que falha encerra a tarefa com `502`, e a function grava essa falha no seu log. Um design que acrescenta um segundo provedor move a tarefa para ele, então as chaves dos provedores, a latência e o fallback são decididos na function. Uma tarefa que chega a seis etapas para com `step_limit`.

### Componentes

- **Functions**: executa o loop do agente e a execução das ferramentas. O loop, o limite de etapas, a regra de aprovação e a decisão de fallback são código na function `ops-agent`, e a chave do provedor é uma variável de ambiente que a function lê.
- **provedor de LLM de terceiros**: a integração que raciocina a cada etapa e escolhe as ferramentas. É a única parte que a Azion não opera, então a disponibilidade, os rate limits e a cobrança dele ficam com o provedor.
- **LangGraph**: o framework de agentes, uma opção de design. Ele é executado dentro da function no lugar de um loop escrito à mão, como faz o LangGraph AI Agent Boilerplate.
- **KV Store**: guarda o estado das etapas entre requisições, no namespace `agent-state`, lido e gravado por uma key por tarefa. Uma tarefa pausada retoma a partir dele, e uma expiração na key descarta uma tarefa sobre a qual ninguém decide.
- **SQL Database**: guarda o histórico das tarefas na tabela `task_events`, uma linha por evento, então a taxa de conclusão, as etapas por tarefa e as aprovações são contadas com SQL. Uma function o abre por uma réplica de leitura, então o histórico é gravado pela API da Azion.
- **APIs internas e ferramentas SaaS**: as integrações sobre as quais as ferramentas agem, aqui o serviço de pedidos em `https://api.example.com/v1`. Cada ferramenta é uma chamada da function, com credenciais que a function lê de variáveis de ambiente.
- **aplicação**: o Platform Resource que serve a API do agente. As regras dela executam a function nos caminhos do agente.

### Outros designs para este caso de uso

- *Tool-calling agent on platform-hosted models*: para equipes que mantêm o agente inteiro na Azion. A function envia a tarefa e as definições de ferramentas a um modelo com tool calling no AI Inference em vez de um provedor, então o raciocínio e a execução das ferramentas ficam dentro da Azion e apenas as tool calls saem dela.

---

## Configure os armazenamentos do agente

O agente guarda dois tipos de dados, e cada um vai para onde o seu padrão de acesso se encaixa:

- **Uma tarefa pausada vai para o KV Store.** A function a lê e grava por uma key, `task:<task-id>`, e apenas quando uma tarefa pausa ou retoma, muito abaixo da uma gravação por segundo que o KV Store aceita em uma key. Cada gravação define `expirationTtl` como `86400`, então uma tarefa sobre a qual ninguém decide é descartada depois de um dia.
- **O histórico das tarefas vai para o SQL Database.** Uma linha por evento permite contar conclusões, etapas e aprovações com SQL. Uma function abre o SQL Database por uma réplica de leitura, então o agente grava as linhas pela API da Azion, com um personal token.

Para criar o namespace, envie o nome dele à API do KV Store:

```bash
curl --request POST \
  --url https://api.azion.com/v4/workspace/kv/namespaces \
  --header 'Accept: application/json' \
  --header 'Authorization: Token [TOKEN VALUE]' \
  --header 'Content-Type: application/json' \
  --data '{"name": "agent-state"}'
```

A API responde `201` com o namespace. Um namespace não pode ser renomeado nem excluído, então confira o nome antes de enviá-lo:

```json
{
  "name": "agent-state",
  "created_at": "2026-01-01T12:00:00.000000",
  "last_modified": "2026-01-01T12:00:00.000000"
}
```

Para criar o banco de dados, envie o nome dele à API do SQL Database:

```bash
curl --request POST \
  --url https://api.azion.com/v4/workspace/sql/databases \
  --header 'Accept: application/json' \
  --header 'Authorization: Token [TOKEN VALUE]' \
  --header 'Content-Type: application/json' \
  --data '{"name":"agent-history"}'
```

A API responde `202` com o banco de dados em `data`. Guarde o `id` dele e envie `GET /v4/workspace/sql/databases/<database-id>` até que `status` mostre `created`, o que leva cerca de 15 segundos. Depois, crie a tabela:

```bash
curl --request POST \
  --url https://api.azion.com/v4/workspace/sql/databases/<database-id>/query \
  --header 'Accept: application/json' \
  --header 'Authorization: Token [TOKEN VALUE]' \
  --header 'Content-Type: application/json' \
  --data '{"statements":["CREATE TABLE task_events (task_id TEXT NOT NULL, event TEXT NOT NULL, steps INTEGER NOT NULL, at TEXT NOT NULL);"]}'
```

A API responde `200` com `"state": "executed"`. Uma instrução que falha também responde `200`, com `error` no lugar de `results` na sua entrada, então leia a entrada antes de continuar.

A conta guarda o namespace `agent-state` e o banco de dados `agent-history` com uma tabela `task_events` vazia.

---

## Configure a function do agente

A function do agente executa o loop em `/api/agent/tasks` e o retoma em `/api/agent/approve`. As decisões que ela aplica:

- **O loop para em seis etapas.** Uma etapa é uma chamada ao provedor mais as ferramentas que ela pede. Seis etapas limitam o tempo em que uma tarefa mantém uma requisição e mantêm uma invocação bem dentro das 50 chamadas `fetch()` de saída que uma function pode fazer.
- **O resultado de uma ferramenta volta como uma mensagem `user`.** A mensagem diz `Result of <tool>: <JSON>`, e o pedido de ferramenta do modelo volta como uma mensagem `assistant` que indica as ferramentas que ele chamou. As duas usam apenas os papéis `system`, `user` e `assistant` que [Invocação de modelos](/pt-br/documentacao/plataforma/ai-inference/invocacao-de-modelos/#objetos-de-mensagem) documenta para o formato de chat.
- **`refund_order` pausa a tarefa.** Uma ferramenta em `APPROVAL_TOOLS` nunca é executada a partir do loop. A function armazena a tarefa e retorna a ação para uma pessoa decidir, e o caminho de aprovação a executa apenas quando a decisão é `approved`.
- **O caminho de aprovação verifica um segredo, e uma ação pendente é executada uma vez.** A function limpa a ação pendente no KV Store antes de executar a ferramenta, então uma segunda aprovação da mesma tarefa responde `404`.
- **Cada ID de tarefa é validado.** O ID é um UUID que a function gerou, e o caminho de aprovação recusa qualquer outro valor antes que o ID chegue a uma instrução SQL.
- **As chaves ficam fora do código.** A chave do provedor, o token do backend, o segredo de quem aprova e o personal token são variáveis de ambiente.
- **Cada evento é uma linha do histórico.** `record()` a grava como [Grave linhas no SQL Database a partir de uma function](/pt-br/documentacao/guias/desenvolvimento-de-aplicacoes/dados/gravar-linhas-no-sql-database-a-partir-de-uma-function/) descreve, com `SQL_DATABASE_ID` e `AZION_TOKEN`. Uma gravação que falha é registrada no log como `history_write_failed` e não interrompe a tarefa.

Para armazenar os valores que a function lê, execute estes comandos com a Azion CLI. Uma key que contém `key`, `token` ou `secret` é armazenada como secret por padrão:

```bash
azion create variables --key "PROVIDER_URL" --value "<provider-chat-completions-url>" --secret false
azion create variables --key "PROVIDER_MODEL" --value "<provider-model-name>" --secret false
azion create variables --key "PROVIDER_API_KEY" --value "<provider-api-key>"
azion create variables --key "BACKEND_API_TOKEN" --value "<backend-token>"
azion create variables --key "APPROVER_SECRET" --value "<approver-secret>"
azion create variables --key "AZION_TOKEN" --value "[TOKEN VALUE]"
azion create variables --key "SQL_DATABASE_ID" --value "<database-id>" --secret false
```

Crie uma function chamada `ops-agent` com este código. A chamada ao provedor envia a chave como `Authorization: Bearer`; mude esse header para o que o seu provedor exige:

```javascript
const MAX_STEPS = 6;
const API_BASE = "https://api.example.com/v1";
const APPROVAL_TOOLS = new Set(["refund_order"]);
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
const SYSTEM_PROMPT =
  "You are an operations agent for an order service. Use the tools to complete the task. " +
  "Answer with a short summary once the task is done.";

const TOOLS = [
  { "type": "function", "function": {
    "name": "get_order",
    "description": "Get one order by its ID, with its status and total.",
    "parameters": { "type": "object", "properties": { "order_id": { "type": "string" } }, "required": ["order_id"] }
  } },
  { "type": "function", "function": {
    "name": "refund_order",
    "description": "Refund an order in full. A person approves every refund before it runs.",
    "parameters": { "type": "object", "properties": { "order_id": { "type": "string" }, "reason": { "type": "string" } }, "required": ["order_id", "reason"] }
  } }
];

async function runTool(name, args) {
  const headers = {
    "Authorization": `Bearer ${Azion.env.get("BACKEND_API_TOKEN")}`,
    "Content-Type": "application/json"
  };
  const order = encodeURIComponent(args.order_id ?? "");
  let response;
  if (name === "get_order") {
    response = await fetch(`${API_BASE}/orders/${order}`, { headers });
  } else if (name === "refund_order") {
    response = await fetch(`${API_BASE}/orders/${order}/refunds`, {
      method: "POST", headers, body: JSON.stringify({ reason: args.reason })
    });
  } else {
    return { error: `unknown tool ${name}` };
  }
  return response.ok ? await response.json() : { error: `the order service answered ${response.status}` };
}

async function callModel(messages) {
  const response = await fetch(Azion.env.get("PROVIDER_URL"), {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${Azion.env.get("PROVIDER_API_KEY")}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({ model: Azion.env.get("PROVIDER_MODEL"), messages, tools: TOOLS })
  });
  if (!response.ok) throw new Error(`provider answered ${response.status}`);
  const body = await response.json();
  return body?.choices?.[0]?.message;
}

async function record(taskId, event, steps) {
  const statement =
    `INSERT INTO task_events (task_id, event, steps, at) VALUES ('${taskId}', '${event}', ${steps}, datetime('now'));`;
  const response = await fetch(
    `https://api.azion.com/v4/workspace/sql/databases/${Azion.env.get("SQL_DATABASE_ID")}/query`,
    {
      method: "POST",
      headers: {
        "Accept": "application/json",
        "Authorization": `Token ${Azion.env.get("AZION_TOKEN")}`,
        "Content-Type": "application/json"
      },
      body: JSON.stringify({ statements: [statement] })
    }
  );
  const body = await response.json();
  const failed = (body.data ?? []).find((entry) => entry.error);
  if (!response.ok || failed) {
    console.log(JSON.stringify({ event: "history_write_failed", taskId, error: failed?.error ?? response.status }));
  }
}

async function runLoop(kv, taskId, state) {
  while (state.steps < MAX_STEPS) {
    state.steps++;
    const message = await callModel(state.messages);
    const calls = message?.tool_calls ?? [];
    if (calls.length === 0) {
      await record(taskId, "completed", state.steps);
      return { task_id: taskId, status: "completed", answer: message?.content ?? "", steps: state.steps };
    }
    state.messages.push({
      role: "assistant",
      content: message.content || `Calling ${calls.map((c) => c.function?.name).join(", ")}.`
    });
    for (const call of calls) {
      const name = call.function?.name;
      const raw = call.function?.arguments ?? {};
      const args = typeof raw === "string" ? JSON.parse(raw) : raw;
      if (APPROVAL_TOOLS.has(name)) {
        state.pending = { name, args };
        await kv.put(`task:${taskId}`, state, { expirationTtl: 86400 });
        await record(taskId, "awaiting_approval", state.steps);
        return { task_id: taskId, status: "awaiting_approval", action: state.pending };
      }
      const result = await runTool(name, args);
      state.messages.push({ role: "user", content: `Result of ${name}: ${JSON.stringify(result)}` });
    }
  }
  await record(taskId, "step_limit", state.steps);
  return { task_id: taskId, status: "step_limit", steps: state.steps };
}

export default {
  async fetch(request, env, ctx) {
    if (request.method !== "POST") {
      return new Response("Method not allowed", { status: 405 });
    }
    const url = new URL(request.url);
    const kv = await Azion.KV.open("agent-state");
    let taskId = null;
    try {
      if (url.pathname === "/api/agent/tasks") {
        const { task } = await request.json();
        if (typeof task !== "string" || task.trim() === "") {
          return Response.json({ error: "task is required" }, { status: 400 });
        }
        taskId = crypto.randomUUID();
        await record(taskId, "started", 0);
        const state = {
          steps: 0,
          pending: null,
          messages: [{ role: "system", content: SYSTEM_PROMPT }, { role: "user", content: task }]
        };
        return Response.json(await runLoop(kv, taskId, state));
      }

      if (url.pathname === "/api/agent/approve") {
        if (request.headers.get("Authorization") !== `Bearer ${Azion.env.get("APPROVER_SECRET")}`) {
          return new Response("Unauthorized", { status: 401 });
        }
        const { task_id, approved } = await request.json();
        if (!UUID.test(task_id ?? "")) {
          return Response.json({ error: "task_id is not a task ID" }, { status: 400 });
        }
        taskId = task_id;
        const state = await kv.get(`task:${taskId}`, "json");
        if (!state?.pending) {
          return Response.json({ error: "no pending action for this task" }, { status: 404 });
        }
        const { name, args } = state.pending;
        state.pending = null;
        await kv.put(`task:${taskId}`, state, { expirationTtl: 86400 });
        await record(taskId, approved === true ? "approved" : "rejected", state.steps);
        const result = approved === true ? await runTool(name, args) : { error: "a person rejected this action" };
        state.messages.push({ role: "user", content: `Result of ${name}: ${JSON.stringify(result)}` });
        return Response.json(await runLoop(kv, taskId, state));
      }

      return new Response("Not found", { status: 404 });
    } catch (error) {
      console.log(JSON.stringify({ event: "task_failed", taskId, error: String(error?.message ?? error) }));
      if (taskId) await record(taskId, "failed", 0);
      return Response.json({ task_id: taskId, status: "failed", error: "the task could not continue" }, { status: 502 });
    }
  },
};
```

Execute a function com estes valores, seguindo [Primeiros passos com Functions](/pt-br/documentacao/plataforma/functions/primeiros-passos/):

- **Instância da function**: `ops-agent`, sem Args.
- **Regra**: uma regra de Request Phase criada como [Execute uma função em um caminho e reverta-o](/pt-br/documentacao/guias/desenvolvimento-de-aplicacoes/functions-e-runtime/executar-uma-funcao-em-um-caminho-e-reverter/) descreve, com estes valores: o nome `agent - tasks and approvals`, um grupo de critérios com `${uri}` *starts with* `/api/agent/` no lugar dos grupos de caminho e de método do guia, e o behavior **Run Function** selecionando a instância `ops-agent`. Os dois caminhos do agente recebem `POST`, e a function responde a qualquer outro método com `405`.

Uma tarefa enviada para `/api/agent/tasks` é executada até que o modelo responda, um reembolso precise de aprovação ou seis etapas passem, e cada início, pausa, decisão e fim chega a `task_events`. Uma regra nova leva alguns minutos para se propagar.

---

## Verifique a configuração

- **Uma tarefa somente de leitura é concluída.** Envie uma tarefa que precisa apenas de `get_order`:

  ```bash
  curl -s -X POST https://www.example.com/api/agent/tasks \
    -H 'Content-Type: application/json' \
    -d '{"task":"What is the status of order <order-id>?"}'
  ```

  A resposta traz `"status":"completed"`, uma `answer` que informa o status do pedido e `steps` igual a 2 ou mais: uma etapa que chama `get_order` e uma que responde.

- **Um reembolso espera por uma pessoa.** Envie `{"task":"Refund order <order-id>, the customer received a damaged item."}` para o mesmo caminho. A resposta traz `"status":"awaiting_approval"`, o `task_id` e uma `action` que indica `refund_order` com o ID do pedido, e o serviço de pedidos não recebeu nenhum reembolso.

- **Uma aprovação retoma a tarefa uma vez.** Aprove o reembolso com o `task_id` dessa resposta:

  ```bash
  curl -s -X POST https://www.example.com/api/agent/approve \
    -H 'Authorization: Bearer <approver-secret>' \
    -H 'Content-Type: application/json' \
    -d '{"task_id":"<task-id>","approved":true}'
  ```

  A resposta traz `"status":"completed"`, e o serviço de pedidos recebeu um reembolso. A mesma requisição enviada de novo responde `404` com `no pending action for this task`, e uma requisição sem o header `Authorization` responde `401`.

- **Cada tarefa é registrada.** Leia os eventos da tarefa de reembolso pela API do SQL Database, com a instrução `SELECT event, steps FROM task_events WHERE task_id = '<task-id>' ORDER BY rowid;`. As linhas de `results` listam `started`, `awaiting_approval`, `approved` e `completed`, nessa ordem.

Quando uma requisição responde com a página da sua própria aplicação, a regra ainda pode estar se propagando. Quando uma tarefa responde `failed`, leia a linha `task_failed` na fonte de dados **Functions Console** do [Real-Time Events](/pt-br/documentacao/plataforma/real-time-events/primeiros-passos/).

---

## Medindo resultados

| Métrica                                             | Onde ler                                                                                                                                                                                              | Como é quando funciona                                                                                                                                  |
| --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Taxa de conclusão de tarefas                        | `SELECT COUNT(DISTINCT task_id) FROM task_events WHERE event = 'completed';` sobre a mesma contagem para `started`, pela API do SQL Database                                                          | A maioria das tarefas é concluída. Um aumento de `step_limit` ou `failed` aponta para um prompt, uma ferramenta ou o provedor                           |
| Etapas por tarefa                                   | O campo `steps` dos eventos `completed` em `task_events`                                                                                                                                              | Estável por tipo de tarefa. Uma tarefa que continua chegando a seis etapas precisa de uma descrição de ferramenta melhor ou de uma tarefa mais restrita |
| Latência por tarefa                                 | O **Request Time** das requisições para `/api/agent/tasks`, na fonte de dados **HTTP Requests** do [Real-Time Events](/pt-br/documentacao/plataforma/real-time-events/fontes-de-dados/#http-requests) | Cresce com as etapas e se mantém estável para o mesmo tipo de tarefa                                                                                    |
| Parcela de ações que precisaram de aprovação humana | A contagem de eventos `awaiting_approval` sobre a contagem de eventos `started`                                                                                                                       | Corresponde à parcela de tarefas que pedem um reembolso, e os eventos `rejected` continuam raros                                                        |

---

## Boas práticas

- **Coloque toda ação que altera dados atrás de aprovação até que o histórico dela esteja limpo.** O modelo decide quando chamar uma ferramenta, então uma ferramenta que movimenta dinheiro ou exclui dados só é executada depois que uma pessoa a aprova. Tire uma ferramenta de `APPROVAL_TOOLS` apenas quando `task_events` mostrar que as aprovações dela são rotineiras.
- **Descreva cada ferramenta para o modelo, não para um desenvolvedor.** O modelo escolhe uma ferramenta pela `description` dela e preenche os `parameters` a partir do schema. Uma descrição que diz quando usar a ferramenta e um schema que marca cada campo como `required` reduzem as etapas que uma tarefa leva.
- **Mantenha o limite de etapas e leia as tarefas que chegam a ele.** Um loop sem limite é executado até que o limite de 5 minutos de tempo de execução da function ou as 50 chamadas de saída dela o interrompam. O evento `step_limit` indica as tarefas a examinar.
- **Grave o histórico das tarefas pela API e verifique cada entrada.** A API do SQL Database responde `200` mesmo quando uma instrução falha, com `error` na entrada dessa instrução, então a function lê cada entrada e registra no log uma gravação que falhou. Para os formatos de erro, consulte [Boas práticas do SQL Database](/pt-br/documentacao/plataforma/sql-database/boas-praticas/).

---

## Guias deste caso de uso

- [Grave linhas no SQL Database a partir de uma function](/pt-br/documentacao/guias/desenvolvimento-de-aplicacoes/dados/gravar-linhas-no-sql-database-a-partir-de-uma-function.md): Grava uma linha de task\_events por evento e verifica cada entrada de instrução.
- [Execute uma função em um caminho e reverta-o](/pt-br/documentacao/guias/desenvolvimento-de-aplicacoes/functions-e-runtime/executar-uma-funcao-em-um-caminho-e-reverter.md): Cria a regra que executa a function do agente em /api/agent/ e a desativa para reverter.
