---
name: azion-pause-um-agente-para-aprovacao-com-kv-store
description: >-
  Execute um loop de agente com tool calling em uma function, pause-o no KV Store antes de uma ação que uma pessoa aprova e registre cada evento no SQL Database.
---

# Pause um agente para aprovação com KV Store

Você executa um loop de agente com tool calling em uma function, armazena uma tarefa no KV Store quando o modelo pede uma ação que uma pessoa precisa aprovar, retoma a tarefa com a decisão e grava cada evento da tarefa no SQL Database, com a Azion CLI e o código de uma function.

---

## 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/).
- O namespace `agent-state` do KV Store. Para criá-lo, consulte [Crie o namespace](/pt-br/documentacao/guias/desenvolvimento-de-aplicacoes/dados/deduplicar-entregas-de-webhook-com-kv-store/#crie-o-namespace).
- O banco de dados `agent-history`, com uma tabela criada pela instrução `CREATE TABLE task_events (task_id TEXT NOT NULL, event TEXT NOT NULL, steps INTEGER NOT NULL, at TEXT NOT NULL);`. Para criar o banco de dados e enviar a instrução, consulte [Crie um banco de dados usando a API](/pt-br/documentacao/guias/desenvolvimento-de-aplicacoes/dados/gerenciar-bancos-dados-edge-sql/#crie-um-banco-de-dados-usando-a-api) e [Crie uma tabela pela API](/pt-br/documentacao/guias/desenvolvimento-de-aplicacoes/dados/criar-tabelas-edge-sql/#crie-uma-tabela-pela-api).
- Um personal token com a permissão **Edit SQL Database**, para as gravações do histórico. 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 exemplos dão 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. Eles usam `www.example.com` para o domínio. Substitua-os pelos seus valores.

---

## Armazene os valores que a function lê

A chave do provedor, o token do backend, o segredo de quem aprova e o personal token ficam fora do código, como variáveis de ambiente.

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
```

A conta guarda as sete variáveis que a function do agente lê com `Azion.env.get()`.

O caso de uso [Criar agentes de IA](/pt-br/documentacao/casos-de-uso/construir-e-executar-workloads-de-ai/criar-agentes-de-ia/) usa os valores deste exemplo.

---

## Crie a function do agente

A function executa o loop em `/api/agent/tasks` e o retoma em `/api/agent/approve`. Uma ferramenta em `APPROVAL_TOOLS` nunca é executada a partir do loop: a function armazena a tarefa sob `task:<task-id>` em `agent-state` por um dia e executa a ferramenta apenas quando uma pessoa a aprova.

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 });
    }
  },
};
```

Para criar a function e a sua instância, siga [Primeiros passos com Functions](/pt-br/documentacao/plataforma/functions/primeiros-passos/) com o nome `ops-agent` e dê à instância o nome `ops-agent`, sem Args.

A aplicação tem uma instância `ops-agent` que executa uma tarefa até que o modelo responda, um reembolso precise de aprovação ou seis etapas passem.

O caso de uso [Criar agentes de IA](/pt-br/documentacao/casos-de-uso/construir-e-executar-workloads-de-ai/criar-agentes-de-ia/) usa os valores deste exemplo.

---

## Execute a function nos caminhos do agente

Uma regra executa a instância nos dois caminhos do agente. Os dois recebem `POST`, e a function responde a qualquer outro método com `405`.

Crie uma regra de Request Phase como [Crie a regra para o caminho e o método](/pt-br/documentacao/guias/desenvolvimento-de-aplicacoes/functions-e-runtime/executar-uma-funcao-em-um-caminho-e-reverter/#crie-a-regra-para-o-caminho-e-o-metodo) 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`.

Uma tarefa enviada para `/api/agent/tasks` executa o agente, e cada início, pausa, decisão e fim chega a `task_events`. Uma regra nova leva alguns minutos para se propagar.

---

## Confirme que uma tarefa pausa e retoma uma vez

Para enviar 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.

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.

Para aprovar o reembolso, envie o `task_id` dessa resposta com o segredo de quem aprova:

```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`.

Para ler os eventos da tarefa de reembolso, envie a instrução `SELECT event, steps FROM task_events WHERE task_id = '<task-id>' ORDER BY rowid;` à API do SQL Database, como mostra [Consulte linhas](/pt-br/documentacao/guias/desenvolvimento-de-aplicacoes/dados/criar-tabelas-edge-sql/#consulte-linhas). As linhas de `results` listam `started`, `awaiting_approval`, `approved` e `completed`, nessa ordem.

O agente pausa antes do reembolso, o executa uma vez depois da aprovação e registra cada evento em `task_events`.

Estas verificações confirmam o caso de uso [Criar agentes de IA](/pt-br/documentacao/casos-de-uso/construir-e-executar-workloads-de-ai/criar-agentes-de-ia/).

---

## Próximos passos

- [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): Armazene as credenciais do banco de dados e verifique o resultado de cada instrução que uma function envia.
- [Criar agentes de IA](/pt-br/documentacao/casos-de-uso/construir-e-executar-workloads-de-ai/criar-agentes-de-ia.md): O design a que esta function serve: o limite de etapas, a regra de aprovação e os armazenamentos, com o motivo de cada um.
